Java性能调优03——Java性能调优工具箱(上):操作系统的工具和分析

本系列均取自《Java性能权威指南》这里只是一个个人备忘笔录

性能分析过程中的一切都要能可视化,从而了解应用内部及应用所在的环境发生了什么。可视化的关键全在于工具,所以性能调优也完全在于工具。

有许多工具可以提供 Java 应用的执行信息,当然全部介绍一遍是不现实的。最重要的工具多数都来自JDK或者开源站点。虽然还有其他开源和商业工具,但为方便起见,本章关注的主要是 JDK 所提供的工具。

操作系统的工具和分析

实际上性能分析的起点与 Java 无关:它是一组操作系统自带的基本监控工具。在基于 Unix 的系统上,有 sar(System Accounting Report)及其组成工具,例如 vmstat、iostat、prstat 等。在 Windows 上,有图形化资源监视器以及像 typeperf 这样的命令行工具。

无论何时运行性能测试,都应该收集操作系统的数据,至少需要收集 CPU、内存和磁盘使用率的信息。如果程序使用网络,还应该收集网络使用率。如果是自动化性能测试,还需要依靠命令行工具(即使是 Windows 系统)。不过,即便可以通过交互方式进行测试,也最好用命令行工具捕获输出,而不是一边盯着 GUI,一边琢磨它的意思。在分析的时候可以再次将这些输出图形化。

CPU使用率

通常 CPU 使用率可以分为两类:用户态时间系统态时间(Windows 上被称作 privileged time)。用户态时间是 CPU 执行应用代码所占时间的百分比,而系统态时间则是 CPU 执行内核代码所占时间的百分比。系统态时间与应用相关,比如应用执行 I/O 操作,系统就会执行内核代码从磁盘读取文件,或者将缓冲数据发送到网络,等等。任何使用底层系统资源的操作,都会导致应用占用更多的系统态时间。

性能调优的目的是,在尽可能短的时间内让 CPU 使用率尽可能地高。这听起来有点不合常理。或许你此时正坐在电脑旁,看着它拼命挣扎,因为 CPU 使用率已经是 100% 了。好,我们先来考虑一下,CPU 使用率到底反映了什么。

首先需要注意的是,CPU 使用率是一段时间内的平均数——5 秒、30 秒,也可能只有 1 秒那么短(不过永远不会比这还要短)。比如,10 分钟内一个程序执行的 CPU 使用率为 50%。如果代码调优之后,CPU 使用率达到了 100%,说明程序的性能翻了倍:程序只需要执行 5 分钟就可以了。如果性能再翻倍,CPU 仍将是 100%,而执行完程序只要 2.5 分钟。CPU 使用率表示程序以多高的效率使用 CPU,所以数字越大,性能越好。

如果在 Linux 桌面系统上运行 vmstat 1(每隔一秒显示一行),可以得到类似如下的几行信息:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 2  0      0 1797836 1229068 1508276 0    0     0     9 2250 3634 41  3 55  0
 2  0      0 1801772 1229076 1508284 0    0     0     8 2304 3683 43  3 54  0
 1  0      0 1813552 1229084 1508284 0    0     3    22 2354 3896 42  3 55  0
 1  0      0 1819628 1229092 1508292 0    0     0    84 2418 3998 43  2 55  0

为了便于说明问题,这个运行示例程序只有一个活跃线程。不过即便有多个线程,也可以应用如下概念。

每秒内,CPU 被占用 450 毫秒(1秒内有42% 的时间执行us用户代码,3% 的时间执行sy系统代码)。相应地,CPU 空闲 550 毫秒。CPU 空闲可能有以下原因。

1.应用被同步原语阻塞,直至锁释放才能继续执行。

2.应用在等待某些东西,例如数据库调用所返回的响应。

3.应用的确是无所事事。

前面 2 种情况通常都可用来识别某些问题。如果竞争降低,或优化数据库使之发送响应更快,程序运行都能变得更快,平均 CPU 使用率也会上升(当然,得假定没有其他继续阻塞应用的问题)。

第 3 点则常常使人疑惑。如果应用有事情做(而不是因为等待锁或者其他资源而无事可干),CPU 就会分配一些周期执行应用代码。这是一般性原则,并不只针对 Java。比如,包含无限循环的简单脚本。这段脚本执行时,将消耗 100% 的 CPU。以下的 Windows 批处理任务就是这么干的:

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

考虑一下,如果这段脚本没有消耗 100%CPU,那意味着什么。意味着,操作系统还有些事可做——它可以打印一行 LOOPING——却选择了空闲。这种情况下,空闲并没有什么好处,如果我们正在进行一些有用(耗时)的计算,那么迫使 CPU 周期性空闲只会使我们得到响应的时间变得更长。

如果在单 CPU 机器上运行上述脚本,多数时候你不会注意到它的运行。不过一旦开启新程序,或者测量其他程序的运行时间,你就能看到影响了。操作系统擅长为争用 CPU 周期的程序分配时间片,但新程序可用的 CPU 变少了,它也就运行得更慢。所以基于这种经验,人们有时会认为,在其他程序可能需要 CPU 周期时预留一些空闲周期,没准是个好主意。

但操作系统无法猜到你接下来想做什么,所以(默认情况下)它会尽可能执行一切而不是让 CPU 空闲。

限制程序所用的 CPU

尽可能利用 CPU 周期运行程序可以使程序性能最大化。不过有时你并不希望如此。比如,你运行 SETI@home1(SETI@home 是一项利用全球联网的计算机共同搜寻地外文明(SETI)的科学实验计划),它将消耗你机器所有可用的 CPU 周期。这在你不干活的时候没事,上网或者写文档的时候也没事,否则就会降低你的生产率。(不妨考虑一下如果你正在玩 CPU 密集型游戏,这样做会发生什么。)

操作系统有许多机制可用来人为限定程序所使用的 CPU——事实上,如果有程序需要使用 CPU,它就会退出空闲周期。进程的优先级也可以改变,所以那些后台任务既不会与你想运行的程序争用 CPU,也不会让 CPU 处于空闲状态。这些技术超出了我们讨论的范围。郑重说一句,SETI@home 可以让你配置优先级,除非你允许,否则它不会真的占用你机器的所有空余周期。

1. Java和单CPU的使用率

再回来讨论 Java 应用——CPU 周期性空闲意味着什么?这依赖于应用的类型。如果应用代码是批处理类型,工作量固定,你应该永远都不会看到 CPU 空闲,因为这意味着没事可做。提高 CPU 使用率,一直都是批处理任务的目的,因为任务会很快完成。如果 CPU 已经达到 100%,你仍然可以寻找优化,使得工作完成的更快(也要尽量保持 100%CPU 使用率)。

如果测试接收请求的服务器应用,就可能出现因无事可做而出现的空闲:例如,Web 服务器已经处理完所有未完成的 HTTP 请求,正在等待下一个请求的时候。这就引入了平均时间。上述 vmstat 的示例来自一个每秒接收一个请求的应用服务器。应用服务器花 450 毫秒处理请求——意思是 CPU 被 100% 占用 450 毫秒,550 毫秒没有占用。这就是所报告的 CPU 被占用 45%。

虽然经常因为 CPU 占用发生的时间粒度很小而难以可视化,但运行负载型应用时 CPU 的行为就是这种爆发式的。如果 CPU 每半秒收到一个请求而平均处理时间为 225 毫秒,也能从宏观层面看到同样的模式。CPU 被占用 225 毫秒,空闲 275 毫秒,再占用 225 毫秒,空闲 275 毫秒:平均来看,被占用 45%,空闲 55%。

如果应用优化之后每个请求只需要 400 毫秒,整体 CPU 使用率就会减少(到 40%)。这是仅有的降低 CPU 使用率有意义的情况——当系统负载量固定并且应用不受外部资源限制的时候。另一方面,优化也使系统可以承担更多负载,最终提高 CPU 使用率。微观来看,这种情况下的优化仍然是使 CPU 使用率在短时间内(执行请求花费 400 毫秒)变为 100%——只是 CPU 峰值持续的时间很短,事实上,大多数工具都不会将其标记为 100%。

2. Java和多CPU的使用率

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

另外在多线程多 CPU 下,需要重点考虑以下 CPU 空闲的情形:即便有事可做,CPU 仍然空闲。这在程序没有更多线程可用的时候可能会出现。典型的情况是,应用以固定尺寸的线程池运行各种任务。每个线程同时只能执行一个任务,当线程被某个任务阻塞时(例如,等待数据库的响应),它就没法捡出新任务执行了。所以此时的情况就是,有任务需要执行(有事可做),却没有线程执行它们,结果就是 CPU 处在空闲时间。

另外在多线程多 CPU 下,需要重点考虑以下 CPU 空闲的情形:即便有事可做,CPU 仍然空闲。这在程序没有更多线程可用的时候可能会出现。典型的情况是,应用以固定尺寸的线程池运行各种任务。每个线程同时只能执行一个任务,当线程被某个任务阻塞时(例如,等待数据库的响应),它就没法捡出新任务执行了。所以此时的情况就是,有任务需要执行(有事可做),却没有线程执行它们,结果就是 CPU 处在空闲时间。

在这个例子中,应该增加线程池的大小。然而,不要假设所有的空闲都是因为 CPU 可用,从而增加线程池以完成更多工作。程序得不到 CPU 周期,还有可能是由于前面提到的两个原因——锁或者外部资源的瓶颈。很重要的一点就是,在决定采取行动前,需要搞清楚为什么程序得不到 CPU。

检查 CPU 使用率是弄清楚应用性能的第一步,但它的用途不仅如此:它可以查看代码的 CPU 使用是否与预期的一样,或者可以指出一些同步或资源问题。

CPU运行队列

Windows 和 Unix 系统都可以监控可运行(意味着没有被 I/O 阻塞、休眠等)的线程数。Unix 系统称之为运行队列(run queue),一些工具的输出中也有运行队列长度。上节 vmstat 的输出中就包括:每行的首个数字就是运行队列长度。Windows 将这个数字称为处理器队列(processor quque),typeperf(还有其他方法)可以报告该信息:

C:>typeperf -si 1 "\System\Processor Queue Length"

"(PDH-CSV 4.0)","\\XUNFEI\System\Processor Queue Length"
"02/29/2020 17:10:01.230","0.000000"
"02/29/2020 17:10:02.238","0.000000"
"02/29/2020 17:10:03.242","0.000000"

前面 vmstat 的输出与此处有很重要的差别:Unix 系统的运行队列长度(vmstat 输出示例中的 1 或 2)是所有正在运行或待运行(即一旦有可用 CPU 就可以运行)的线程数。示例中至少有一个线程试图运行:即以单线程执行应用。因此,运行队列长度至少是 1。记住,运行队列反映的是机器上所有东西的运行情况,所以示例输出中有时会看到运行队列长度为 2,因为此时有其他线程(来自其他完全隔离的进程)试图运行。

而在 Windows 中,处理器队列长度就不包括正在运行的线程。因此,上述 typeperf 示例输出中的处理器队列长度就是 0,即便机器上同样有一个单线程应用的线程一直在执行。

如果试图运行的线程数超过了可用的 CPU,性能就会下降。一般来说,Windows 的处理器队列长度最好为 0,小于或等于 Unix 系统 CPU 的数目。不过,这不是硬性的规定。有些系统或其他进程会周期性出现,在这瞬间数字会有提高,这对性能不会有实质性的影响。但是,如果在相当长时间内运行队列很长,说明系统已经过载,这时你应该检查系统,减少机器正在处理的工作量(将工作转移到其他机器或者优化代码)。

小结

1.检查应用性能时,首先应该审查 CPU 时间。

2.优化代码的目的是提升而不是降低(更短时间段内的)CPU 使用率。

3.在试图深入优化应用前,应该先弄清楚为何 CPU 使用率低。

磁盘使用率

监控磁盘使用率有两个目的。第一个目的与应用本身有关:如果应用正在做大量的磁盘 I/O 操作,那 I/O 就很容易成为瓶颈。想了解何时磁盘 I/O 是瓶颈非常困难,因为这取决于应用的行为。如果应用往磁盘写数据时没有有效的缓冲,磁盘 I/O 的统计数据就会非常低。但是,如果应用执行的 I/O 超过了磁盘的承载,磁盘 I/O 的统计数据就会非常高。请注意,这两种情形的性能都需要提升。

有些系统的基本 I/O 监控要好于其他系统。这是 Linux 系统 iostat 的部分输出:

% iostat -xm 5
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

应用正在往磁盘 sda 写数据。乍一看,磁盘统计数据还不错。w_await——每次 I/O 写的时间——相当低(6.08 毫秒),磁盘使用率只有 1.04%。(可接受的值取决于物理磁盘,在低于 15 毫秒时,我的台式机系统 5200 RPM 的磁盘可以工作得很好。)但这里有条线索可以看出点问题:系统在内核花费了 37.89% 的时间。一种可能是系统正在进行其他 I/O(在其他程序中)。如果这个系统时间都来自被测的应用,说明某些低效率的事正在发生。

另一条线索是,系统每秒写为 24.2:当每秒写入只有 0.14 MB 时,这算很大的数字。这说明 I/O 已经是瓶颈,接下来应该检查应用是如何写的。

如果磁盘速度赶不上 I/O 请求,问题的另外一面就出现了:

% 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

Linux 好处在于可以立即告诉我们磁盘的使用率为 100%。它也能告诉我们进程的 47.89% 的事件在 iowait(表示正在等待磁盘)。

即便其他平台只能提供原始数据,也可以告诉我们哪里出错了:完成 I/O(w_await)用时 871 毫秒,队列很长,磁盘每秒写入 81 MB 数据。所有这些都说明磁盘 I/O 有问题,应用(或者可能是系统的其他部分)的 I/O 必须要降低。

监控磁盘使用率的第二个理由是——即便预计应用不会有很高的 I/O——有助于监控系统是否在进行内存交换。计算机的物理内存量是固定的,但它们可以用大得多的虚拟内存来运行一系列应用。应用会保留更多超过它们实际所需的内存量,并且它们通常也只使用分配给它们的内存的一部分。这两种情况下,操作系统可以将不用的内存保留在磁盘上,在需要时换页到物理内存。

大多数情况下,这种类型的内存管理可以工作得很好,特别是交互式应用和 GUI 程序(这一点是很有好处的,否则你的笔记本就需要比实际多得多的内存)。这种管理方式对服务器类应用来说效果稍差,因为这些应用需要更多内存。由于 Java 堆的原因,这种管理方式对于任何 Java 程序(包括你桌面上运行的基于)来说都比较糟糕。

正在内存交换的系统——从主内存移动数据到磁盘或者反过来——一般来说,性能比较差。还有其他系统工具可以报告系统交换,例如 vmstat 输出中有两列(si 是换进,so 是换出)可以警告我们系统是否正在交换。磁盘活动说明内存交换可能正在发生。

小结

1. 对于所有应用来说,监控磁盘使用率非常重要。即便不直接写磁盘的应用,系统交换仍然会影响它们的性能。

2. 写入磁盘的应用遇到瓶颈,是因为写入数据的效率不高(吞吐量太低),或者是因为写入太多数据(吞吐量太高)。

网络使用率

如果应用运行时需要网络——比如 Java EE 应用服务器——你也必须监控网络流量。网络使用率类似磁盘流量:应用可能没有充分利用网络所以带宽很低,或者写入某网络接口的总数据量超过了它所能处理的量。

不幸的是,由于标准的系统工具通常只能显示某个网络接口发送和接收的数据报数和字节数,所以它们在监控网络流量方面差强人意。虽然这些信息有用,但无法告诉我们网络是没有充分利用,还是过度使用。

Unix 系统监控网络的基本工具是 netstat(大多数 Linux 发行版中还没有包括 netstat,必须单独获得)。Windows 上则可以在脚本中使用 typeperf,监控网络使用率——不过 GUI 的优势可以显现出来的,标准的Windows 资源监视器显示网络使用百分比的图。不幸的是,GUI 在自动性能测试场景中几乎没有什么帮助。

幸运的是,有许多开源和商业工具可以监控网络带宽。Unix 里一个受欢迎的命令行工具就是 nicstat,它可以显示每个网络接口的流量概要,包括网络接口的使用度:

% nicstat 5
Time      Int       rKB/s   wKB/s   rPk/s   wPk/s   rAvs    wAvs    %Util   Sat
17:05:17  e1000g1   225.7   176.2   905.0   922.5   255.4   195.6   0.33    0.00

示例中的 e1000g1 是 1000 MB 接口,使用率非常低(0.33%)。这个工具(以及其他类似的工具)可以用来计算接口的使用率。在上述输出中,接口的数据写入速率是 225.7 Kbps,读取速率是 176.2 Kbps。对于 1000 MB 的网络,相除以后可以得到使用率 0.33%,nicstat 也能自动算出接口的带宽。

typeperf 或 netstat 这样的工具可以报告读取和写入的数据,但是要计算网络使用率,你必须自己用脚本计算接口的带宽。虽然一般工具报告的单位是字节 / 秒(Bps),但请切记,带宽的单位是位 / 秒(bps)。1000 兆位网络每秒处理 125 兆字节(MB)。本示例中,读为 0.22 MBps,写为 0.16 MBps,相加然后除以 125 得出使用率为 0.33%。所以 nicstat(或类似工具)没有什么神奇的,只是更便于使用而已。

网络无法支持 100% 的使用率。对本地以太局域网来说,承受的网络使用率超过 40% 就意味着接口饱和了。如果网络是包交换或使用不同的传输介质,网络使用率的最大值就可能会不同,因此最好是评估网络架构之后再确定合适的值。这个值与 Java 无关,只是简单利用网络参数和操作系统接口。

磁盘小结

1.对基于网络的应用来说,务必要监控网络以确保它不是瓶颈。


2.往网络写数据的应用遇到瓶颈,可能是因为写数据的效率太低(吞吐量太低),也可能是因为写入了太多的数据(吞吐量太高)。

Linux中常见的基本工具命令

(详细的见以后的Linux性能优化相关):

CPU相关:

负载均衡:uptime、top、/proc/loadavg

系统CPU使用率:vmstat、mpstat、top、sar、/proc/stat

进程CPU使用率:top、ps、pidstat、htop、atop

系统上下文切换:vmstat

进程上下文切换:pidstat

软中断:top、mpstat、/proc/softirqs

硬中断:vmstat、/proc/interrupts

网络:dstat、sar、tcpdump

I/O:dstat、sar

CPU缓存:perf

CPU数:lscpu、/proc/cpuinfo

事件剖析:perf、火焰图、execsnoop

动态追踪:ftrace、bcc、systemtap

内存工具

已用/剩余内存:free、vmstat、sar、/proc/meminfo

虚拟/常驻/共享内存:ps、top、pidstat、/proc/pid/stat、/proc/pid/status

进程内存分布:pmap、/proc/pid/maps

进程swap换出内存:top、/proc/pid/status

进程缺页异常:ps、top、pidstat

系统换页情况:sar

缓存/缓冲区使用量:free、vmstat、sar、cachestat

缓存/缓冲区命中率:cachetop

swap已用空间和剩余空间:free,sar

swap换入换出:vmstat、sar

内存泄漏检测:memleak、valgrind

指定文件的缓存大小:pcstat

磁盘工具

磁盘空间大小使用情况:df

索引节点大小使用情况:df

页缓存和可回收slab缓存:/proc/meminfo、sar、vmstat

缓冲区:/proc/meminfo、sar、vmstat

目录项、索引节点、文件系统的缓存:iostat、sar、dstat、/proc/diskstats/proc/meminfo、sar、vmstat

进程I/O大小及I/O延迟:pidstat、iotop

块设备I/O事件跟踪:blktrace

进程I/O系统调用跟踪:strace、pref trace

进程块设备I/O大小跟踪:biosnoop、biotop

动态追踪:ftrace、bcc、systemtap

网络工具

吞吐量(BPS):sar、nethogs、iftop、/proc/net/dev

吞吐量(PPS):sar、/proc/net/dev

网络连接数:netsat、ss

网络延迟:ping、hping3

连接跟踪数:conntrack、/proc/sys/net/netfilter/nf_conntrack_count、/proc/sys/net/netfilter/nf_conntrack_max

路由:mtr、traceroute、route

DNS:dig、nslookup

防火墙和NAT:iptables

网卡选项:ethtool

网络抓包:tcpdump、wireshark

动态追踪:ftrace、bcc、systemtap

更新时间:2020-03-07 09:58:45

本文由 寻非 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.zhouning.group/archives/java性能调优03java性能调优工具箱
最后更新:2020-03-07 09:58:45

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×