代码位置
书位置蓝奏云

3 Java性能调优工具箱

3.1 操作系统的工具和分析

无论何时运行性能测试,都应该收集操作系统的数据,至少需要收集CPU,内存和磁盘使用率的信息。如果程序使用网络,还应该收集网络使用率。

3.1.1 CPU使用率

通常CPU使用率可以分为两类:用户态和系统态时间。用户态时间是CPU执行应用代码所占时间的百分比,而系统态时间则是CPU执行内核代码所占时间的百分比。系统态时间与应用相关,比如应用执行I/O操作,系统就会执行内核代码从磁盘读取文件,或者将缓存数据发送到网络,等。任何使用底层系统资源的操作,都会导致应用占用更多的系统态时间。
性能调优的的目的是,在尽可能短的时间内让CPU使用率尽可能地高。这听起来有点不合常理。或许你此时正在电脑旁,看着它拼命挣扎,因为CPU使用率已经是100%了。CPU使用率到底反映了什么。

首先需要注意的是,CPU使用率是一段时间内的平均数——5秒,30秒,也可能只有1秒那么短。比如10分钟内一个程序执行的CPU使用率为50%。如果代码调优之后,CPU使用率达到了100%。CPU使用率表示程序以多高的效率使用CPU,所以数字越大,性能越好。
**
如果在Linux桌面系统上运行vmstat 1 可以得到类似如下的几行信息(每隔一秒显示一行)

  1. [root@centos64 ~]# vmstat 1 -S M
  2. procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
  3. r b swpd free buff cache si so bi bo in cs us sy id wa st
  4. 2 0 0 224 37 483 0 0 4 5 1 3 0 0 99 0 0
  5. 0 0 0 224 37 483 0 0 0 0 191 231 0 0 100 0 0
  6. 0 0 0 224 37 483 0 0 0 48 216 312 1 0 99 0 0

-S 是设置单位
image.png
每秒内,CPU被占用450毫秒(42%的时间执行用户代码,3%的时间执行系统代码),相应地,CPU空闲550毫秒,CPU空闲可能有以下原因。

  • 应用被同步原语阻塞,直至锁释放才能继续执行。
  • 应用在等待某些东旭,例如数据库调用所返回的响应。
  • 应用的确无所事事。

前面2种情况通常可用来识别某些问题。如果竞争降低,或优化数据库使之发送响应更快,程序运行都能变得更快,平均CPU使用率也会上升。
第3点则常常使人疑惑。如果应用有事情做(而不是因为等待锁或者其他资源而无事可干),CPU就会分配一些周期执行应用代码。这是一般性原则,

ECHO OFF
:BEGIN
ECHO LOOPING 
GOTO BEGIN
REM We never get here?
ECHO DONE

上述代码片段是windows上循环。

1 Java和单CPU的使用率

讨论Java应用——CPU周期性空闲意味着什么?这依赖于应用的类型。如果应用代码是批处理类型,工作量固定,你应该永远都不会看到CPU空闲,因为这意味着没事可做。提高CPU使用率,一直都是批处理任务的目的,因为任务很快完成。如果CPU已经达到100%,你仍然可以寻找优化,使得工作完成的更快(也要尽量保持100%CPU使用率)
如果测试接受请求的服务器应用,就可能出现因无事可做而出现的空闲:例如WEB服务器已经处理完所有的HTTP请求,正在等待下一个请求的时候。这就引入平均时间,上述vmstat的示例来自一个每秒接受一个请求的应用服务器,应用服务器花450毫秒处理请求——意思是CPU被100%占用450毫秒,550毫秒没有占用,这就是报告的CPU被占用45%。

2 Java和多CPU的使用率

上面的例子是假定在单个CPU上运行的单线程,但概念与一般情况下多CPU多线程相同。多线程倾向于以有趣的方式平均使用CPU。但一般来说,多CPU多线程的目的仍然是通过不阻塞线程来提高CPU使用率,或者在线程完成工作等待更多任务是降低CPU使用率。

3.1.2 CPU运行队列

window和unix系统都可以监控可运行的线程数,Unix系统称之为运行队列。vmstat 输出的的每行首个数字就是运行队列长度。

Unix系统的运行队列长度(vmstat输出示例中的1或2)是所有正在运行或等待的线程数。示例中至少有1个线程视图运行:即以单线程执行应用。因此,运行队列长度至少是1。运行队列反映的是机器上所有东西的运行情况,所以示例输出中有时会看到运行队列长度为2,因为此时有其他线程(来自其他完全隔离的进程)视图运行。

3.1.3 磁盘使用率

image.png

应用正在往磁盘sta写数据。乍一看,磁盘统计数据还不错。w_await——每次I/O写的时间——相当低(6.08毫秒),磁盘使用率只有1.04%。但这里有条线索可以看出问题:系统在内核花费了37.89%的时间。一种可能是系统正在进行其他I/O。
另一条线索是,系统每秒写为24.2。当每秒写入只有0.14MB时,这算很大的数字,说明I/O已经是瓶颈。

3.1.4 网络使用率

image.png
unix中一个受欢迎的命令行工具就是nicstat,它可以显示每个网络接口的流量概要,包括网络接口的使用度:
示例(上图)e1100g1是1000MB的网络,使用率非常低(0.33%)。示例中数据的写入速率是225.7 Kbps,读取速率是176.2Kbps。

网络无法支持100%的使用率。对于本地以太局域网来说,承受的网络使用率超过40%就意味着接口饱和。

3.2 Java监控工具

JDK自带的工具

  • jcmd 用来打印Java进程所涉及的基本类,线程和VM信息,它适用于脚本。
  • jconsole 提供JVM活动的图形化视图,包括线程的使用,类的使用和GC活动
  • jhat 读取内存堆转储,并有助于分析,这是事后使用的工具
  • jmap 提供对转储其其他JVM内存使用的信息,
  • jinfo 查看JVM的系统属性,可以动态设置一些系统属性,可适用于脚本
  • jstack 转储Java进程的栈信息,可适用于脚本
  • jstat 提供GC和类装载活动的信息
  • jvisualvm

3.2.1 基本的VM信息

JVM工具可以提供JVM进程的基本运行信息:运行了多久、使用哪些JVM标志,以及JVM的系统属性。


#  查看JVM运行的时长
jcmd process_id VM.uptime

# 显示 System.getProperties()的各个条目
jcmd process_id VM.system_properties  
jinfo -sysprops process_id

#JVM版本
jcmd process_id VM.version

3.2.2 线程信息

jconsole和jvisualvm可以实时显示应用中运行的线程的数量。
查看运行线程的栈信息,对于判断线程是否被阻塞很有用,可以通过jstack获取栈信息:
jstack process_id
也可以通过jcmd获取栈信息
jcmd process_id Thread.print

9.4 JVM线程调优