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 种事件类型。根据发布的版本,事件种类的精确数字会略微有所不同,这里是一些最有用的。

以下每种事件类型的说明都包括两点。事件可以像其他工具如 jconsole 和 jcmd 那样收集基本信息。这类信息在第一点中描述。第二点则描述的是事件提供者,这在 JFR 之外很难获得。

Classloading

装载或卸载的类的数量

哪个类装载器装载的类,装载单个类需要的时间

Thread statistics

创建和销毁的线程数,线程转储

被锁阻塞的线程(以及阻塞住它们的特定锁)

Throwables

应用所用的异常错误类

有多少异常和错误被抛出了,以及它们生成时的栈追踪信息

TLAB allocation

堆分配的数量和本地线程分配缓冲的大小

堆中分配的对象和分配它们的栈追踪信息

File and socket I/O

执行 I/O 所用的时间

每次读 / 写调用所用的时间,读或写很长时间的特定文件或 socket Monitor blocked

等待管程的线程

在特定管程上阻塞的线程以及它们被阻塞的时间

Code cache

代码缓存的大小以及包含多少量

从代码缓存中移走的 Java 方法,代码缓存配置

Code compilation

哪个方法已被编译,OSR 编译和编译的时长

除了 JFR 其他工具也包括的信息,但这是从多个源头获取的统一信息

Garbage collection

GC 的时间,包括单个阶段,代的大小

除了 JFR 其他工具也包括的信息,但这是从多个工具获取的统一信息

Profiling

探查和采样分析器

不像真的性能分析器那样有很多信息,但 JFR 分析器提供了很好的更高层次的概要信息

开启JFR

在 Oracle JVM 的商业版本中,JFR 初始时为关闭。为了开启它,可以在应用的启动命令行上添加标志

-XX:+UnlockCommercialFeatures 
-XX:+flightRecorder

这会开启 JFR 特性,但直到记录过程自身开始时才会记录信息。记录可以通过 GUI 或者命令行产生。

通过JMC开启JFR

在JMC启动后点击左侧的飞行记录即可....

飞行记录器可以设为两种模式之一:固定持续时间(这个例子是 1 分钟),或者持续进行。对于持续记录,会有一个循环缓冲,缓冲包含最近的事件,并且这些事件在设置的持续时间和大小内。

为了进行主动分析——意味着你将开启记录,然后还会产生一些工作量,或者在 JVM 热身之后——应该用固定持续时间的方式记录,在负载测试试验中开启记录。这些记录将对测试期间 JVM 如何响应给出很好的指示。

持续记录最适合于响应性分析(reactive analysis)。JVM 会保留最近的事件,然后转储为响应某些事件的记录。例如,WebLogic 应用服务器可以在遇到应用服务器异常事件时(例如处理超过 5 分钟的请求),触发转储记录。你可以针对任何事件,设立自己的监控工具转储这些记录。

通过命令行开启JFR

开启 JFR 之后(选项 -XX:+flightRecorder),有两种方法控制记录应该在何时以及如何发生。JVM 用 -XX:+flightRecorderOptions=string 参数方式启动时,可以控制这些记录参数。这对响应性记录来说最为有用。参数中的 string 是一列逗号分隔的名字 - 值对,可以用以下选项:

name=name:用以标识记录的名字。

defaultrecording=<true|false>:表示初始时是否开启记录。默认为 false。对于响应性分析,应该设为 true。

settings=path:JFR 设置文件的文件名。

delay=time:记录开始前延迟的时间量(例如,30 秒,1 小时)。

duration=time:记录持续的时间。

filename=path:记录文件名。

compress=<true|false>:记录是否开启压缩(gzip)。默认为 false。

maxage=time:循环缓冲中保留记录数据的最长时间。

maxsize=size记录循环缓冲的最大尺寸(例如,1023 K,1 M)。

在某些情况下,像上述那样设置默认记录很有用,但为了更大的灵活性,所有选项可在程序运行时(假设 -XX:+flightRecorder 已先指定),用 jcmd 来控制。

要开启飞行记录:

% jcmd process_id JFR.start [options_list]

options_list 是一组用逗号分隔的名字 - 值对,控制记录如何进行。可能的选项与命令行使用 -XX:+flightRecorderOptions=string 时的标志完全一样。

如果开启持续记录,可以在任何时间,通过以下命令将当前循环缓冲里的数据转储到文件中:

% jcmd process_id JFR.dump [options_list]

选项列表包括:

name=name:在这个名字下的记录已经开始。

recording=n:JFR 记录的编号。

filename=path:转储文件的位置。

对于给定的进程,可能开启了多个 JFR 记录。以下命令可查看开启的记录:

% jcmd process_id JFR.check [verbose]

这个例子中,用记录启动时的名字来标识它们,也可以任意指定记录的编号(可以在其他 JFR 命令中使用)。 最后是进程放弃记录的命令:

% jcmd process_id JFR.stop [options_list]

这个命令接受以下选项:

name=name:停止记录的名字。

recording=n:停止记录的编号(可由 JFR.check 获得)。

discard=boolean:如果为 true,则丢弃数据而不是写到前面所提供的文件中(如果有的话)。

filename=path:数据写到给定的路径上。

回归检查运行系统的时候,在自动化性能测试系统中运行这些命令并生成记录,会很有用。

选择JFR事件

除了自带提供的事件外,JFR 是可扩展的:应用可以定义自己的事件。因此,依据所考虑的应用,你的 JFR 视线可以显示更多类型的事件。例如,WebLogic 应用服务器开启一组应用服务器事件: DBC 操作、HTTP 操作等。这些事件和前面所讨论的 JFR 事件一样:它们可以单个开启,可以有关联的阈值,等等。同样,最新版本的 JVM 可能会添加一些这里没有讨论到的事件。

详情请查阅最新的产品文档。

收集事件自然会引入一些开销。触发事件收集的阈值——因为增加了事件——在开启 JFR 记录时也会增加开销。默认记录中不会收集所有的事件(6 个最耗资源的事件没有开启),基于事件的阈值有些高。这使得默认记录的开销小于 1%。

有时额外的开销是值得的。例如,考虑 TLAB 事件,它可以帮助你判断对象是否正直接分配到老年代,但默认情况下这些事件没有开启记录。同样,默认记录开启了性能分析事件,但只有每 20 毫秒——可以给出适当的概览,但也可能导致采样错误。

JFR 捕获的事件(包括事件的阈值)都定义在模板中(可通过命令行的设置选项选择)。JFR 自带了两个模板:默认模板(限制了事件使得开销效率 1%)和性能分析模板(大多数基于阈值的事件被设置为每 10 毫秒触发)。这个性能分析模板的开销估计是 2%(而你的数据通常也都不一样,我观察到的典型开销要比这小得多)。


模板由 jmc 模板管理器管理。你可能已经注意到如上图中启动模板管理器的按钮了。模板保存在两个地方:$HOME/.jmc/ 目录(用户本地)下,和 $JAVA_HOME/jre/lib/jfr(JVM 全局)下。模板管理器允许你选择全局模板(可以说模板“在服务器上”),选择本地模板,或定义新模板。定义模板时,来回多看看可用的事件,依据需要开启(或关闭)它们,还可以选择根据需要来设置事件阈值。


上图显示 File Read 事件开启,并且阈值为 15 毫秒:读取文件超过该值的就会触发事件。这个事件也可以配置为给 File Read 事件产生栈追踪信息。这增加了开销——这就是为何抓取事件栈追踪信息是可配置的选项。

事件模板是简单的 XML 文件,所以判断模板中哪个事件开启(以及阈值和栈追踪信息配置)的最好办法是阅读 XML 文件。XML 作为配置文件,使得本地模板可以在一台机器上定义,然后复制到全局模板目录下,供团队中的其他人使用。

JRF快速小结

  1. 由于 JFR 内建于 JVM,所以可以最大可能地查看 JVM 内部。
  2. 像其他工具一样,JFR 给应用引入了一些开销。对于日常使用,可以开启 JFR,以较低的开销收集大量信息。
  3. JFR 用于性能分析,但它在生产系统中也很有用,所以你可以检查那些导致失败的事件。

小结

好的工具是性能分析成功的关键。对于工具所能告诉我们的信息,本章只是皮毛。关键是要记住以下几点。

没有完美的工具,每种工具也各有所长。分析器 X 可能适合很多应用,但可能有些情况下就会缺少一些信息,而分析器 Y 就能很清楚地给出这些信息。你总是需要灵活使用。

命令行监控工具可以自动收集重要数据,确保在自动化性能测试中能够收集监控数据。

工具的演变非常快:本章提到的一些工具可能已经过时了(或者已经被新的更强大的工具所取代)。这个领域很重要的一点就是要与时俱进。

除这里说的JMC外其他诸如 JConsole,VisualVm,JProfiler等 很多可以自行了解.

更新时间:2020-03-07 09:59:20

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

评论

Your browser is out of date!

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

×