Java 性能分析工具 , 第 1 部分: 操作系统工具

时间:2022-06-14 01:39:42

引言

性能分析的前提是将应用程序内部的运行状况以及应用运行环境的状况以一种可视化的方式更加直接的展现出来,如何来达到这种可视化的展示呢?我们需要配合使用操作系统中集成的程序监控工具和 Java 中内置的监控分析工具来进行 Java 程序的性能分析。本文为系列文章,共三篇分别介绍这几类工具。在本文中将介绍操作系统中的性能监控工具。

操作系统中的程序性能监控工具并非只针对于 Java 程序,适用于所有运行其中的程序。在基于 UNIX 的操作系统中,有许多命令行工具可以用来监控程序的运行状况,例如 sar, vmstat, iostat,prstat 等等。在 Windows 操作系统中,既有图形化用户界面的资源监控器 Perfmon(Performance Monitor),也有如 typeperf 的命令行工具。

进行性能测试时,我们需要通过操作系统提供的工具收集操作系统中的各类资源监控数据,包括 CPU、内存和硬盘的使用数据,如果被测试程序使用了网络,还需要收集网络使用数据。只有收集的数据足够完整和充分,性能测试的结果才会更准确,性能分析也会更加容易进行。下面将首先介绍在 UNIX/类 UNIX 系统中各类资源的监控和分析的方法。

Linux 系统资源的监控

CPU 使用率

CPU 使用时间分为两类:用户时间(User Time)和系统时间(System Time),系统时间在 Windows 系统中被称为特权时间(Privileged Time)。用户时间为 CPU 执行应用程序代码的时间,而系统时间则为 CPU 执行操作系统内核代码的时间比例。系统时间与应用程序本身有关,例如当应用程序执行 I/O 操作时,操作系统内核将会执行从硬盘读取文件的代码,或者执行向网络数据缓存中写入数据的代码。应用程序中任何需要使用操作系统底层资源的行为都会导致应用程序占用更多的系统时间。

性能调优的终极目标是在单位时间内最大限度提高 CPU 使用率。CPU 使用率是在一个特定时间间隔内的平均值,这个时间间隔可以是 30 秒,也可以是 1 秒。比如,一个程序需要 10 分钟执行完成,在此期间该程序的 CPU 使用率为 50%。当对程序代码优化之后,CPU 使用率提高为 100%,那么该程序的性能将提升一倍,只需要 5 分钟执行完成。当该程序再次优化代码使用 2 个 CPU,CPU 的使用率依然为 100%,那么该程序将只需要 2.5 分钟执行结束。从这个例子可以看出,CPU 使用率能够反应程序使用 CPU 的效率,CPU 使用率越高程序性能越好,反之亦然。

在 Linux 操作系统中执行 vmstat 5 命令,将会得到如清单 1 中的数据(每 5 秒增加一行)。为了易于理解,此例中的程序只使用单线程运行,在多线程环境中同样适用。从示例数据中第一行数据可以知道,在 5 秒内,CPU 一共被使用了 2.25 秒(5*(37%+8%)),其中 37%的时间用于执行用户代码,8%的时间用户执行系统代码。剩余的 2.75 秒 CPU 处于闲置状态(idle)。

清单 1. vmstat 5 命令结果
 procs -----------memory--------------- ----swap---- ---io--- -----system------ ----------CPU-------
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 236456 2259632 200052 730348 0 0 1 6 1 1 37 8 55 0 0
2 0 236456 2259624 200052 730348 0 0 0 10 179 332 40 7 53 0 0
2 0 236456 2259624 200052 730348 0 0 0 20 180 356 56 7 37 0 0

以下三点原因会造成 CPU 闲置:

  1. 应用程序被线程的同步操作阻塞,直到锁被释放;

  2. 应用程在等待某些请求的响应,例如等待数据查询请求的响应;

  3. 应用程序无事可做;

前两种情况比较容易理解,也有对应的调优方式。针对原因一,如果能够减少锁的竞争,或者调整数据库返回请求资源的性能,那么应用程序会运行的更快;对于原因二,优化请求响应方,提高响应速度;那么在其他条件不变的情况下,应用程序会运行的更快,CPU 使用率也会提高。

第三种情况当应用程序有事去做时,CPU 将利用 CPU 周期去执行应用程序的代码。这是一条通用的规则。当执行一段无限循环的代码(如下所示)时,它将会再消耗一个 CPU 100%的时间。如果 CPU 的使用率并没有达到 100%,意味着操作系统应该执行无限循环,但它并没有去做而是处于闲置状态。这种情况对于无限循环并没有多少影响,但是如果我们的程序是用来计算一个表达式的结果,那么这种情况将会导致计算的速度变慢。

清单 2. 无限循环示例
#!/bin/bash
while true
do
echo“In the loop…”
done

当在一台单核机器上运行清单 2 中的代码,绝大多数时间我们不会注意到它在运行。但是如果启动另一个程序或者监控另一个程序的性能时,这种影响就会体现出来。操作系统善于利用时间切片程序来竞争 CPU 周期,但是最新启动的程序只能获得极少的的可用 CPU 周期。有一种解决问题的方案,那就是留出一定比例的闲置 CPU 周期以防有其他程序需要使用 CPU。但是这种方案暴露出来的问题就是操作系统无法知晓下一步操作,操作系统只能去执行当前所有的操作而不会留出闲置的 CPU 周期。

Java 和单 CPU 使用率

我们回到 Java 应用程序,周期性的 CPU 闲置意味着什么呢?这取决于应用程序的类型。对于有固定作业量的批处理程序,除非全部作业完成,否则 CPU 不会有闲置时间。提高 CPU 的使用率可以使批处理程序更快地完成。如果 CPU 使用率已经达到 100%,我们可以在保持 CPU 使用率 100%的前提下从其他方面进行优化使程序完成地更快。

对于接收请求的服务器类型的应用程序,在没有请求到来的时候,CPU 会处于闲置状态。举例来说,当 Web 服务器处理完当前所有 HTTP 请求处于等待下一个请求的状态时,CPU 为闲置状态。从这里可以理解 CPU 使用率为何为一定时间间隔内的平均数值。上文 vmstat 示例中的数据采集自一个应用服务器的运行过程中,这个服务器每 5 秒接收到一个请求,花费 2.25 秒处理,这意味着在这 2.25 秒内 CPU 的使用率为 100%,而在剩下的 2.75 内使用率为 0。由此计算得出 CPU 使用率为 45%。

这种情况总是发生在非常短的时间间隔内,因此很难被发现,但是这种类似应用服务器的程序总是按照此方式运行。当我们降低时间间隔,上述应用服务每 2.5 秒接受一个请求同时花费 1.125 秒处理请求,剩余的 1.375 秒 CPU 处于闲置状态。平均下来,CPU 平均使用率依然为 45%,55%的时间处于闲置状态。

优化应用服务器之后,处理每个请求只需要 2 秒,CPU 使用率将降至 40%。降低 CPU 使用率是我们优化程序代码的目标。只有在单位时间内,没有外部资源约束的应用程序负载固定。从另一方面来讲,优化这种应用程序可以适当增加程序负载来提升 CPU 使用率。这样一来,可以看出这种优化策略依然遵循前文的规则,即在尽可能短的时间内使 CPU 使用率尽可能高。

Java 和多 CPU 使用率

调优多线程程序的目标依然是尽可能的提高每个 CPU 的使用率,使 CPU 尽可能少的被阻塞。在多核多线程环境中,当 CPU 处于闲置状态时需要多考虑的是即使应用程序有作业未完成,CPU 依然会处于闲置状态,因为该应用程序中没有可用的线程来处理作业。最典型的例子为一个拥有固定大小线程池的应用程序运行数量变化的任务。每个线程每次只能处理一个任务,如果此线程被某些操作阻塞,这个线程不能转而去处理另一个任务。在这种情况下就会出现没有可用线程来处理未完成的任务。因此会导致 CPU 处于闲置状态。对于此种情形应该考虑如何增加线程池的大小来完成更多的任务。

监控 CPU 使用率只是理解应用程序性能的第一步,这只能确定代码的 CPU 使用率是否达到开发人员的期望,或者找到代码中存在的同步问题和资源问题。

CPU Run Queue

在 Windows 和 UNIX 系统中都可以监控当前可以执行任务的线程数。UNIX 系统中称为 Run Queue,有很多工具可以查到此数据。例如前文中的 vmstat,每行的第一个数字即是 Run Queue 的长度。Windows 系统中称之为 Processor Queue,可以通过 typeperf 命令查到。

Windows 与 UNIX 的区别是:在 UNIX 中,Run Queue 长度为当前正在运行和可以运行的线程数,所以这个长度最小为 1;而在 Windows 中,Processor Queue 长度并不包括正在运行的线程数,因此 Processor Queue 长度最小值为 0。

当可用线程数大于可用 CPU 数量,性能就会下降。所以在 Windows 中 Processor Queue 长度为 0,在 UNIX 中 Run Queue 长度等于 CPU 数的情况性能达到最好。但这并不绝对,因为系统程序中会周期性运行导致此数值增大,单对应用程序的影响不大。如果 Run Queue 长度长时间远远大于 CPU 数,表示机器负载过大,应该适当减少当前机器的作业量。

硬盘使用率

监控硬盘使用率有两个重要的目标,一是应用程序本身,如果应用程序进行了非常多的硬盘 I/O 操作,很容易推断出应用程序的性能瓶颈在于 I/O。

需要进行详细的监控才能发现应用程序的性能瓶颈在于 I/O。当应用程序没有高效地使用缓存来进行硬盘写操作时,硬盘 I/O 的数据将会非常低。但是当应用程序进行的 I/O 操作数超出了硬盘能够处理的数量,硬盘 I/O 数据将会非常高。这两种情况都需要进行调优。

在 Linux 系统中执行 iostat -xm 5 命令可以得到清单 3 中的数据:

清单 3. iostat –xm 5 命令结果一
 avg-CPU: %user %nice %system %iowait %steal %idle
18.20 0.00 40.20 0.00 0.00 51.60
Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s
sda 0.00 0.20 0.00 34.60 0.10 0.23
avgrq-sz avgqu-sz await svctm %util
8.35 0.00 5.04 0.04 2.02

应用程序在向硬盘 sda 中写入数据,看上去硬盘写入的时间情况还不错,每次写入等待时间(await)为 5.04 毫秒,硬盘使用率也仅为 2.02%。但仔细来看,系统执行内核代码用掉 40.2%的时间,这是意味着应用程序中存在低效的写操作。系统每秒进行 34.60(w/s)次写操作,但是只写入了 0.23MB(wMB/s) 数据。可以判断 I/O 是应用程序的性能瓶颈所在。下一步将分析应用程序如何进行写操作。

再看另一组数据(清单 4),硬盘使用率(%util)达到 100%,等待硬盘的时间占到了 49.81%(%iowait),应用程序每秒写入 60.45mb 数据,这些数据共同证明 I/O 是应用程序的性能瓶颈所在,必须减少如此大量的 I/O 操作。

清单 4. iostat –xm 5 命令结果二
 avg-CPU: %user %nice %system %iowait %steal %idle
40.20 0.00 5.70 49.81 0.00 54.10
Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s
sda 0.00 0.20 0.00 134.60 0.10 60.45
avgrq-sz avgqu-sz await svctm %util
727.24 68.46 798.04 5.67 100

监控硬盘使用率的另一个作用是得知系统是否在进行交换(swapping),计算机拥有固定数量的物理内存,但是它可以运行使用内存远远大于其物理内存的一些应用程序。应用程序占有的内存往往多于它们真正需要的内存,在这种情况下,操作系统将这些没有被用到内存挪入硬盘,当需要的时候将它们通过换页换进物理内存中。对于大多数应用程序,这种内存管理方式是不错的,但是对于服务器类型的应用程序而言,这种方式就显得尤为糟糕,因为由于 Java 内存堆的存在,服务器类型的应用往往需要用到非常多的物理内存。

由于需要将硬盘中的数据与物理内存中的数据进行交换,会严重影响系统性能。vmstat 命令的结果中 si,so 两列数据表示了换入物理内存和换出物理内存的数据量。通过这些数据可以知道系统是否在进行交换。

网络使用率

如果应用程序运行过程中使用了网络,在进行性能监控时必须监控系统网络传输使用率。网络传输类似于硬盘传输,低效地使用网络传输会造成网络带宽不足;如果网络传输的数据量超过了其所能负载的上限同样会造成网络传输性能瓶颈。

操作系统内置的网络监控工具只能获得某个网络接口接收和发送的包数和字节数。通过这些信息还不足以确定网络负载正常还是负载过大。

在 UNIX 系统中,基本的网络监控工具是 netstat。

当然,还有非常多的第三方网络监控工具,nicstat 就是 UNIX 系统中使用很广泛的一个命令行工具,通过这个工具可以得到指定网络接口的使用率。

执行 nicstat 5 命令,得到清单 5 中的数据,从数据中可以看到,网络接口 e1000g1 为 1000MB 接口,该接口使用率只有 2.98%(%Util),每秒钟通过此接口读入 156.4Kb 数据,写入 256.9Kb 数据,通过这些数据,可以明确得出网络接口的带宽以及使用率。

清单 5. nicstat 5 命令结果
 Time Int rKB/s wKB/s rPk/s wPk/s rAvs wAvs %Util Sat
17:05:17 e1000g1 156.4 256.9 875.0 909.5 215.4 175.3 2.98 0.00

如果只用 netstat 命令,可以获得每秒读写的数据量,但是必须知道网络带宽并且通过额外的脚本才能计算得出网络接口的使用率。在计算过程中需要注意,带宽单位为位每秒(bps),因此 1000Mb 带宽每秒可以传输 125MB 数据。而在 nicstat 已经帮我们做了类似的计算。

网络传输无法支撑 100%的使用率,在本地以太网网络,超过 40%的使用率就被认为接口饱和。使用其他媒介进行网络传输的饱和使用率需要咨询网络架构师。Java 程序只是使用操作系统的网络接口进行传输,并不能决定网络使用率的饱和值。

Windows 系统资源的监控

下面将主要介绍 Windows 系统的系统监控工具 Perfmon。Perfmon 是 Windows 系统自带的性能监控工具,可以监控包括上文所述的各类系统资源的使用情况,并提供图形化的用户展示界面。Perfmon 包括性能监视器、计数器日志、跟踪日志和警报四个部分。

1. 性能监视器

在 Windows 系统的命令行中运行 perfmon.msc 命令即可启动 Perfmon 的性能监视器的用户界面。通过性能监视器,可以对 CPU、硬盘、网络的资源进行实时监控。具体的分析方式与 Linux 系统中类似,在此不再赘述。使用下面将要介绍的计数器日志可以保存这些监控数据

性能监视器的另一个功能是将计数器日志保存的数据以图形化的形式展现给用户。通过“查看当前活动”或者“查看日志数据”功能来指定监控的资源。

2. 计数器日志

虽然性能监视器可以实时监控系统资源,但并不能保存监控数据,如果需要持续对系统的监控数据采样,必须使用 Perfmon 的计数器日志功能。用户可以使用系统监视器或者其他工具对计数器日志保存的数据的进行分析。

3. 跟踪日志

通过跟踪日志功能,用户可以跟踪某些重要的系统事件,可以跟踪指定的应用程序。跟踪日志默认被保存为扩展名为.etl 二进制文件,可以使用 tracert 命令对文件进行分析,并生成 CSV 格式的 Dump 文件。

目前必须通过编辑系统注册表的形式配置跟踪的应用程序以及保存日志文件的路径。

4. 警报

当某个计数器的性能监控数据达到预先设定的阀值时,将会触发 Perfmon 的警报,警报是指预先设定的动作,如发送电子邮件、运行指定的命令等动作。也可以将警报动作设置为将警报作为系统事件记录,这样就可以在事件查看器中查看警报的内容。针对不同的应用可以指定不同的警报策略。例如当 CPU 的闲置时间低于 80%时触发警报,将发送邮件给系统维护人员;或者当内存使用率高于 90%时出发警报,执行 typeperf 命令收集保存数据。

必须是管理员用户才能使用 Perfmon,Perfmon 有两种部署方式:本地监控模式和远程监控模式。

在本地监控模式中,日志文件被默认保存在 C:\perflogs 目录中,可以在“日志文件”下修改此目录。本地监控生成的日志文件既可以在本机使用性能监视器进行分析,也可以传输到其他分析平台中分析。

在远程监控模式中,在建立监控主机与被监控主机之间信任关系并打开远程访问控制的前提下,可以对局域网内的多台目标监控机器进行集中采样监控。但随之而来的是安全隐患,所以在访问控制比较严格的环境下,远程监控模式难以实施。

在部署过程中还需考虑日志文件存储问题,设置合适的采样间隔时间,如果设置过小,日志文件会快速递增,如果设置过大,监控数据会出现较大误差。

Perfmon 有两种管理方式:控制台管理和命令行管理,如前文所述,通过运行 perfmon.msc 可以打开控制台管理器,并根据监控策略管理控制台。通过命令 Logman 可以在命令行中创建、启动、停止日志 Session。主要包括 create, start, stop, delete, query, update 这些参数,具体的使用方法参考 Logman 帮助文档。除了 Logman 命令,Typeperf 命令也是 Windows 中常用的系统功能监控命令,通过此命令可以获得 Perfmon 中所有资源当前的性能数据,但不能生成日志和设置警报。我们将 Typeperf 的输出重定向至文本文件中,使用第三方工具进行分析。Typeperf 可以配合其他性能工具一起使用,通过定制计划任务执行此命令可以定时获得系统的性能数据。

总结

本文在性能监测和优化方面给出了多种方法,并结合 CPU 时间特性阐述了其性能影响的因素和原因。总体上讲,性能监测首先应当从应用程序运行时所消耗的 CPU 时间来入手,剖析其运行状态,以及资源消耗的瓶颈在哪里。其次,以 CPU 使用率的提升为目标来优化代码。

性能监测还可以通过监测硬盘的使用情况来获得,大量硬盘的读写将会产生应用程序的性能问题,这就要求我们的程序设计人员在设计程序的时候尽量降低减少磁盘的读写操作,以及读写的数据量,并采用缓存交换的数据的形式提升应用程序性能。

对基于网络的应用程序,监测其网络传输的开销,也是性能监测的一种方法。大量数据在网络层面上的交换也会带来性能上的开销。

最后,本文讲解了 Java 自带的性能分析工具的使用,在使用这些工具的时候请大家铭记一点,

没有一个完美的工具来帮您完全理解应用程序的整体性能问题,实际工作中我们可能需要结合多种工具来完成一个应用程序的性能分析。不同的工具有不同的方向,全方面的理解和使用才能够更好的完成分析任务。