Java性能调优05——Java性能调优工具箱(下):Java任务控制

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

JMC 是源自 JRockit JVM 的一套监控和管理工具,Oracle 在 2013 年发布 JAVA 7u40(Java 7 Update 40) 时将其包含在 JDK 中,在JDK11前的版本不需要额外下载。该工具主要由三个组件构成:Java 进程浏览器、JMX 控制台和 Java Flight 记录器。最早JMC只在商业版中有,但是在18年5月5日 宣布开源(一说是oracle为了解散JMC团队,甩手给openJdk)。在jdk11前版本中自带(bin目录下),但是在JDK11后被删除不在JDK中自带。

11及以后版本可以自行下载:
👉 JMC

JMC 的程序(jmc)开启一个窗口以显示当前机器上的 JVM 进程,你可以选择一个或多个进行监控。下图是 JMC 监控 jetbrains idea 的一个实例。

idea JMC

图中显示了 JMC 正在监控的基本信息:CPU 使用率和堆使用量。但请注意,CPU 图包括了当前机器上所有的 CPU。这是监控的关键特性:JMC 可以监控整个系统,而不仅是所选择的 JVM。顶部的“面板”可以配置,以显示 JVM 的信息(所有的统计信息,包括 GC、类加载、线程使用情况、堆使用量等)以及操作系统相关的信息(机器总的 CPU 和内存使用量、页面交换、平均负载等)。

像其他监控工具一样,JMC 可以向任何被监控应用的 MBean 发起 Java Management Extensions(JMX)调用。

远程

使用JDK1.8中的jmc对远程Java进行进行JMX监控及JFR需要具备两个前提条件,即支持JMX连接和启动JFR飞行记录。

  1. 支持JMX连接的配置
    要配置远程Java进程支持JMX连接,需要在启动Java进程时加入以下属性:
-Dcom.sun.management.jmxremote.port=7091
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

上述配置的意义是开启JMX连接端口7091,同时配置不需要ssl安全认证方式连接(一般我们的监控的服务器都是在本地局域网中,所以没有必要配置ssl安全认证方式连接,若要配置ssl安全认证方式连接,还需要配置连接的用户名及密码等)
2. 取消锁定商业功能以开启飞行记录的配置
取消锁定商业功能有两种方式:
a. 使用jcmd命令解锁
使用用 VM.unlock_commercial_features 参数取消锁定商业功能,命令如下:
jcmd pid VM.unlock_commercial_features
b. 应用程序启动参数中增加解锁参数
解锁参数如下:
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder

Java飞行记录器

JMC 的关键特性是 Java 飞行记录器(Java Flight Recorder,JFR)。正像它名字所暗示的,JFR 数据是 JVM 的历史事件,这些可以用来诊断 JVM 的历史性能和操作。

JFR 的基本操作是开启一组事件(例如,线程等待某个锁而被阻塞的事件)。每当选择的事件发生时,就会保存相应的数据(保存在内存或文件中)。数据流保存在循环缓冲中,所以只有最近的事件。JMC 可以显示这些事件——实时从 JVM 获取或者从文件读取——你可以对这些事件进行分析,诊断性能问题。

所有这些——事件的类别、循环缓冲的大小、数据保存在哪里等——都可以通过 JVM 的不同参数、JMC 的 GUI 以及程序运行时的 jcmd 命令来控制。JFR 的默认设置只有很低的开销:程序性能的 1% 以下。如果开启的事件变多,或者事件触发的阈值更改等,开销也会随之变化。本节的后面将详细讨论这些配置,我们先了解一下这些事件显示出来是什么样的,从而更容易理解 JFR 是如何工作的。

JFR概览

JMC 加载记录后,首先看到的是基本的监控概要,如下:

我们以运行了一个 Servlet的GlassFish 为例,下图所展示的与 JMC 基本监控时的显示非常类似。仪表盘显示的是 CPU 使用率和堆使用量,仪表盘上方是事件的时间线(用一系列的垂直条表示)。时间线可以放大以显示重点区域。本示例记录的数据区间为 6 分钟,放大之后,显示的是到记录结束之前 1:06 分钟内的数据。

图中的 CPU 占用率大体可以看出正在做什么。GlashFish 的 JVM 在图的底部(平均大约 70% 的占用率),而机器的 CPU 为 100%。面板底部还有一些选项卡可以一探究竟:系统属性,JFR 记录的实际数据。窗口左边面板上的图标更为重要:通过这些图标对应的视图可以了解应用内部的行为。

JFR内存视图

这里汇集的信息范围很广。下图只显示了内存视图中的一个面板。

图中显示,内存的使用量随着新生代的回收而发生很有规律的波动(而更重要的是,该应用的内存堆整体没有什么增长:没有对象被提升到老年代)。左下角面板显示 JFR 记录期间发生的所有垃圾收集(每行表示一次收集),包括持续的时间和收集的类型(这个例子始终是 ParallelScavenge)。当选中某一行事件时,右下角面板会进一步分拆显示事件的详细信息,包括这次垃圾收集的所有特定阶段和花了多长时间。

正如大家从这页的不同选项卡上所看到的,还有许多其他有价值的信息:有多少引用对象和多长时间被清理,并发收集过程中是否有对象提升或疏散失败(evacuation failure),GC 算法的配置参数(包括代的大小和 Survivor 空间的配置),甚至已分配的特定种类对象的信息。当你读到后面章节时,希望你还能牢记这里所讨论的这个工具所能诊断的问题。如果你需要弄明白为何 CMS 出现并发失败而执行全部 GC(因为晋升失败?),或者 JVM 如何调整晋升阈值(tenuring threshold),或者关于 GC 及其行为的几乎所有数据,JFR 都将回答。

JFR代码视图

代码视图

这个视图的第一个选项卡显示了一组包名,这是一个许多分析器中都没有的重要特性。应用的 41% 的时间用在 java.math 包内的计算上。视图底部还有其他传统分析视图的选项卡:最热的方法和被分析代码的调用树。

而这个图显示nio包占用50%。与其他分析器不同的是,JFR 提供了查看代码内部的其他模式。“异常错误”(Throwable)选项卡提供了应用内部异常处理的视图(后面将讨论为什么异常处理过多会导致糟糕的性能)。还有些选项卡告诉我们编译器正在做什么,包括代码缓存的内部视图。

以及一些其他显示如线程、I/O 和系统事件——但这其中的大部分只是为 JFR 记录的真实事件提供了更好的视图。

事件视图

窗口左边的面板可以过滤需要显示的事件。这里只选择应用级别的事件。请注意,在开始记录时,只包括特定种类的事件:在这个时候,我们进行事后的过滤。

本示例的 66 秒间隔中,应用的 JVM 产生了 10 612 个事件,JDK 库产生了 1536 个事件,靠近窗口底部的是在这个期间生成的 6 种事件类型。我已经讨论过这个示例的线程停止(thread-park)和管程等待(monitor-wait)事件为什么这么高。可以忽略这些事件(事实上,通常它们是很好的过滤条件)。那其他事件呢?

过了 66 秒后,应用的多个线程写 socket 花费了 40 秒。对于在 4 个 CPU 上运行的应用服务器来说,这并不是一个不合理的数字(也就是说,264 秒),但意味着可以通过向客户端写入较少的数据来改善性能(使用第 10 章列出的技术)。

与此类似,多个线程读 socket 用了 143 秒。这个数字听起来比较糟糕,值得进一步追踪这些事件,不过最终证实有些线程使用了阻塞 I/O 读取预计周期性到达的管理请求。这些请求之间——很长一段时间——线程被 read() 方法阻塞。所以这里的读取时间变得可以接受:如果使用性能分析器,你应该判断大量的线程被 I/O 阻塞是预料之中的,还是说明存在性能问题。

还有监视器阻塞事件。锁竞争分为两种级别:首先是线程自旋等待锁,然后线程使用(进程中称为锁膨胀)一些 CPU 或 OS 特定的代码等待锁。标准的分析器可以提示处于那种情形,因为自旋时间包括在该方法的 CPU 时间内。本地分析器可以给出锁膨胀的信息,但这是不确定的(例如,Oracle Studio 分析器在 Solaris 上运行的很好,但 Linux 上缺少必要的能提供与 Solaris 相同信息的操作系统钩子)。但 JVM 可以直接向 JFR 提供所有的这些数据。

使用锁可见性。通常来说,理所当然的那些 JFR 事件是直接来自于 JVM 的事件,它们给应用增加了一个可见性级别,这是其他工具不能提供的。在jmc发布时可以监控 77 种事件类型。根据发布的版本,事