Java性能优化指南系列(二):Java 性能分析工具

Posted qq_28674045

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java性能优化指南系列(二):Java 性能分析工具相关的知识,希望对你有一定的参考价值。

进行JAVA程序性能分析的时候,我们一般都会使用各种不同的工具。它们大部分都是可视化的,使得我们可以直观地看到应用程序的内部和运行环境到底执行了什么操作,所以性能分析(性能调优)是依赖于工具的。在第2章,我强调了基于数据驱动的性能测试是非常重要的,我们必须测试应用的性能并理解每个指标的含义。性能分析和数据驱动非常类似,为了提升应用程序的性能,我们必须获取应用运行的相关数据。如何获取这些数据并理解它们是本章的主题。【本章重点介绍JDK中提供的性能分析工具】

操作系统工具及其分析

  • 程序分析的起点并不是和Java相关的,而是和操作系统相关。在基于Unix的操作系统上,和性能分析相关的工具都来自于sarSystem Accounting Report),它包括:vmstat iostat prstat等等。
  • 在进行性能测试的时候,操作系统的相关数据必须要收集起来。这些数据至少包括:CPU,内存以及硬盘使用率。如果程序使用了网络,网络使用率等相关信息也要收集起来。

CPU使用率

  • CPU使用率可以分为两部分:用户时间和系统时间。这里要注意:系统时间也是和应用相关的,比如:应用使用网络传输数据,内核将会执行网络模块的代码来获取数据并将数据拷贝到内核缓冲区;如果应用启动了很多线程,线程调度也会占用系统时间。
  • 性能测试的目标是在最短的时间内让CPU越高越好。这个听起来和直觉可能有点相反,为什么要让CPU达到100%啊。那就让我们理解下CPU使用率的含义是什么?
  • 首先我们要记住:CPU使用率是一段时间内的平均值,这段时间可能是5秒,30秒,甚至是1秒(不可能小于1秒)。下面我就来解释为什么CPU使用率越高越好?假设一个程序在10分钟的运行过程中,CPU使用率(注意:一段时间的平均值)为50%。如果我们调优了代码,CPU使用率达到了100%,那么程序的性能就提升了1倍,因为它将会在5分钟左右完成所有工作。如果性能又提升了一倍,程序将会在100% CPU使用率的情况下,2.5分钟左右完成所有工作。CPU使用率表示的是程序使用CPU的效率,因此是越高越好(当然要排除异常情况)。
  • 如果我们运行vmstat 1(每1秒运行1次),在Linux环境下将会产生类似下面的结果:

% vmstat 1

procs -----------memory---------- ---swap-------io---- -system-- ----cpu----

r b swpd free buff cache si so bi bo in cs us sy idwa

2 0 0 1797836 1229068 1508276 0 0 0 9 2250 3634 41 355 0

2 0 0 1801772 1229076 1508284 0 0 0 8 2304 3683 43 354 0

1 0 0 1813552 1229084 1508284 0 0 3 22 2354 3896 423 55 0

1 0 0 1819628 1229092 1508292 0 0 0 84 2418 3998 432 55 0

上图说明,在1秒内,有450ms CPU是忙的(42%用于执行用户代码,3%用于执行系统代码);其它550ms CPU都是空闲的,CPU空闲可能有以下原因:

  • 应用阻塞在同步器上,不能执行直到锁被释放
  • 应用在等待一个事情,比如:数据库的响应
  • 应用没什么事情可做
  • 上面3种情况的前2个是我们要关注的,如果能减少锁竞争,或数据能够更快地发送响应,程序将会运行的更快,应用的CPU使用率就会上升(当然,这里假设没有其它方面会阻塞应用)

上面的第3点常常会带来疑惑,如果应用有事情可做,CPU会执行程序的代码。这个是基本的原则,不仅仅准对Java。比如:我们写了一个Windows下的脚本,就是简单的无限循环,如果执行它,CPU立马就100%了,


如果CPU没有达到100%呢,那么就意味着操作系统还有其它一些事情要做(比如:上例中的打印LOOPING),但是却选择了idleCPUidle状态对于应用是没有好处的,如果我们运行的是计算密集型的应用,CPUidle的,意味着我们需要使用更长时间得到结果。

  • 如果你在单核CPU上面运行这个命令,你应该很难看到CPU会空闲;但是如果你尝试启动一个新的程序或测试另外一个应用的性能,你就可以看到效果了。操作系统非常擅长时间片程序,它让这些程序来竞争CPU周期。不过对于刚刚创建的程序,得到CPU的可能性比较小,因此它会比正常运行的情况下稍慢些。这个经验会让大家认为空出一些CPU周期是一个好主意,以便让这些CPU用于处理其它事情上。但是操作系统不知道后续将会发生什么,所以它会执行任何它能做的事情,而不是让CPU idle

限制一个程序的CPU:当在有CPU周期的时候运行程序会提升程序的性能。但是有时候我们不想要这样,比如:我们在运行SETI@home,它会消耗所有可获取的CPU,如果我们在工作还好,如果我们在上网,写文档或者打游戏,那影响就很明显。为了解决这个问题,一些操作系统的机制可以人为设定程序使用的CPU;程序的优先级也可以让一些后台程序不会在有更高优先级的程序正在运行的时候消耗更多的CPU

Java和单个CPU使用率

  • 上面所说的周期性空闲CPU的含义是什么呢?答案依赖于应用类型。
  • 如果应用是计算密集型的,由于它有固定的工作需要完成,你应该看不到idle CPU,因为它一直忙于计算任务。使得CPU利用率变得更高是计算密集型应用的目标,因为这意味着工作完成的更快。如果CPU已经达到了100%,我们还可以采用其它优化方式来让工作完成的更快(也尽量使CPU使用率为100%)。
  • 如果应用是web应用,存在idle time的可能性就很大,因为它很有可能无事可做。例如:当处理完了所有请求之后,它基本上就处于空闲状态,直到下一个请求到来。假设服务器上有一个web应用,它每秒处理一个请求,下面是vmstat命令的输出。它使用450ms来处理请求,也就是说CPU450ms100%的,其余550ms0%CPU的利用率就是45%

% vmstat 1

procs -----------memory---------- ---swap-------io---- -system-- ----cpu----

r b swpd free buff cache si so bi bo in cs us sy idwa

2 0 0 1797836 1229068 1508276 0 0 0 9 2250 3634 41 355 0

2 0 0 1801772 1229076 1508284 0 0 0 8 2304 3683 43 354 0

1 0 0 1813552 1229084 1508284 0 0 3 22 2354 3896 423 55 0

1 0 0 1819628 1229092 1508292 0 0 0 84 2418 3998 432 55 0

尽管时间粒度很小(只有几百毫秒),但是当负荷很高的时候,从宏观上看CPU的使用率(每0.5s接收一个请求,处理每个请求的平均时间为225ms)是45%(每半秒内,225ms是忙的,275ms是空闲的)。

  • 假设我们对应用进行了优化,将每个请求的处理时间缩短到400ms,那么CPU的使用率也会降低到40%,因为请求量没有发生变化,CPU100%的时间变短了。同时,这个优化也使得我们可以增加系统的负荷,从而提升CPU的利用率。

Java和多CPU使用率

  • 上面的例子是假设一个线程运行在一个CPU上,对于多个线程运行在多个CPU上面的情况是一样的。多线程影响CPU使用率的方式很有趣,其中一个例子是在第5章,它显示了多个GC线程对CPU使用率的影响。运行在多核CPU上面的多线程的目标也是尽量使CPU使用率更高(让每个独立的线程不发生阻塞)。
  • 在多核多线程CPU的情况下,当CPUidle的时候,有一点是我们应该关注的:即使有很多请求等待处理,但是CPU也可能是idle的。发生这种情况的原因是应用中没有更多的空闲线程可以用来处理新的请求。典型的情况是,一个拥有固定数目的线程池应用,在上面运行着不同的任务。一个线程在一个时刻只能运行一个任务,如果线程运行的任务阻塞了,那么这个线程就没办法获取另外一个任务进行处理(必须处理完当前任务)。这样就会导致虽然CPU是空闲的,但是没有线程可以用来处理新的请求。
  • 如果出现了上面的情况,我们应该要增加线程池的大小,但是不要看到CPU空闲了就增加线程池的大小,这里是为了完成更多的任务,才需要增加线程池的大小。程序不能获取CPU的原因可能有两个---锁或外部资源。在决定采取优化措施之前,弄清楚为什么程序不能获取CPU是很重要的。
  • 查看CPU的利用率是对应用进行性能调优的第一步,但它也只是用来确定:应用消耗的CPU资源是否符合预期或代码是否存在锁/外部资源的问题。

CPU RunQueue

  • 比如:vmstat输出中的procs部分,r指的就是RunQueue的大小。如果r的大小比CPU的数目还多,那么系统的性能将会开始下降。

[root@localhost ~]# vmstat

procs -----------memory---------- ---swap-------io---- --system-- -----cpu-----

 r  b  swpd   free   buff cache   si   so   bi    bo   in  cs us sy id wa st

 4  1 5619980 811116 219740 19671656    0   0   164   194   0    0  2  294  1 0

 

磁盘使用率

  • 监控磁盘I/O有两个目标。一是,如果应用磁盘I/O操作比较多,需要及时发现磁盘I/O是否成为性能瓶颈;二是,监控系统是否做了swap操作(此时即使应用的磁盘I/O不多,发生了swap的时候,磁盘I/O也会变得很高)。
  • 要确定磁盘I/O是否为系统的瓶颈是比较棘手的,因为它依赖于应用的行为。如果应用对写入磁盘的数据没有进行很好的缓存,磁盘I/O的统计信息会很低;如果应用需要处理的I/O超过了磁盘的最大处理能力,磁盘I/O的统计信息就会很高。这两种情况的应用性能都可以进行提升。
  • Linux下使用iostat命令来监控I/O

[signal@keyuanfenxi02 log]$ iostat-xm 5

Linux2.6.32-431.el6.x86_64 (keyuanfenxi02)    20160610  _x86_64_        (64 CPU)

avg-cpu: %user   %nice   %system   %iowait   %steal   %idle

                 23.45   0.00      37.89         0.10          0.00     38.56

Device: rrqm/s    wrqm/s   r/s    w/s      rMB/s

   sda     0.00         11.60      0.60  24.20  0.02

wMB/s   avgrq-sz   avgqu-sz   await   r_await   w_await   svctm   %util

0.14        13.35        0.15           6.06     5.33         6.08         0.42       1.04

 

从上面的输出来看,使用率只有1.04%w_await也只有6.08ms,似乎情况很好;但是系统使用了37.89%CPU时间用在sys上面,而且每秒写入0.14MB的空间,需要写入24.2次,这个显然是不合理的(也就是上面说的数据没有得到很好缓存的情况)。

  • 下面的输出,显然是磁盘的处理能力跟不上应用的I/O请求,I/O使用率为100%,在I/O上等待了47.89%

iostat-xm 5

avg-cpu: %user   %nice   %system   %iowait   %steal   %idle

                 35.05   0.00     7.85             47.89       0.00     9.20

Device: rrqm/s   wrqm/s    r/s    w/s         rMB/s

sda         0.00       0.20       1.00    163.40   0.00

wMB/s   avgrq-sz   avgqu-sz   await     r_await   w_await   svctm   %util

81.09      1010.19    142.74     866.47   97.60       871.17      6.08     100.00