开源 Java 性能分析器比较:VisualVM、JMC 和 async-profiler
在本文中,我将介绍性能分析的基本概念和不同类型的开源 Java 分析器,让你可以根据自己的需要选择最适合的分析器,并了解这些工具大致的工作原理。
在 2023 年伦敦 QCon 演讲“你的 Java 应用程序很慢吗?试试这些开源分析器”中,我深入探讨过这个话题,也介绍了不同的性能查看器。本文是基于那次演讲整理而成。
分析器的目的是获取有关程序执行的信息,让开发人员可以看到一个方法在给定的时间段内执行了多长时间。
但它们是如何做到这一点的呢?有两种方法:程序插桩和抽样。
获取性能分析概要的一种方法是,对于开发人员感兴趣的每个方法,记录其进入和退出时间。
当想要知道程序的特定部分花费了多长时间时,许多开发人员都会使用这种检测方法。
在这种方法中,下面的方法:
void methodA() {
// … // 做工作
}
会被修改成:
void methodA() {
long start = System.currentTimeMillis();
// … // 做工作
long duration = System.currentTimeMillis() - start;
System.out.println(“methodA took “ + duration + “ms”);
}
这种修改可以用于基本的时间测量。尽管如此,在嵌套测量方法时,它提供的信息很少,因为了解方法之间的关系也很有趣,例如methodB()
由methodA()
在几秒钟内执行。因此,我们需要记录每次进入和退出相关方法的日志。这些日志会关联到时间戳和当前线程。
插桩分析器的思想是将这种代码修改的过程自动化:它将logEntry()
和logExit()
方法的调用插入到方法的字节码中。这些方法是分析器运行时库的一部分。通常,这种插入是在运行时完成的,即在类加载时通过插桩代理完成。然后,分析器将methodA()
修改为:
void methodA() {
logEntry(“methodA”);
// … // 做工作
logExit(“methodA”);
}
插桩分析器的优点是它们对所有 JVM 都有效,因为它们可以用纯 Java 实现。但它们有一个缺点,即插入的方法调用会导致显著的性能损失并严重影响结果。因此,在最近几十年里,纯插桩分析器的流行度已然消退。如今,现代分析器大多都是抽样分析器。
另一种分析器是抽样分析器,它们会在被分析程序执行时进行抽样。这类分析器会定期向 JVM 请求当前运行程序的堆栈,通常是每 10 毫秒到 20 毫秒一次。然后,分析器会使用这些信息来估算性能。这种方法的主要缺点是:运行时间比较短的方法可能不会在性能分析概要中出现。
抽样分析器的主要优点是:它们不会修改程序,开销比较小,不会对结果产生明显的影响。
现代抽样分析器通常每 10 到 20 毫秒一次循环运行以下代码:
抽样分析器每次迭代都会获取当前的(Java)线程列表。然后,它会随机选择一个线程子集进行抽样。通常,这个子集的大小在 5 到 8 之间,因为每次迭代对太多线程进行抽样会增加运行分析器的性能影响。在分析具有大量线程的应用程序时,请注意这一点。
然后,分析器向每个选定的线程发送一个信号,这将导致它们停下来调用信号处理程序。此信号处理程序会获取并存储其线程的堆栈跟踪。在每次迭代结束时,分析器会收集所有堆栈跟踪信息并进行后处理。
实现抽样分析器还有其他的方法,但我这里介绍的是使用最广泛且精度最佳的技术。
目前,最著名的开源分析器有 3 个:VisualVM、async-profiler 和 JDK Flight Recorder(JFR)。这些分析器都处于积极开发过程中,可用于各种应用程序。它们都是抽样分析器。VisualVM 是唯一支持插桩分析的分析器。
我们可以区分下“外部”和“内置”分析器:外部分析器不是直接实现到 JVM 中,而是使用 API 来收集特定线程的堆栈跟踪信息。对于只使用 API 的分析器,同一个版本可以用于不同的 JVM 版本和供应商(如 OpenJDK 和 OpenJ9)。
最著名的外部分析器有两个:VisualVM 和 async-profiler;它们的主要区别在于它们使用的 API。VisualVM 使用官方的 Java 管理扩展(JMX)来获取线程的堆栈跟踪信息。另一方面,async-profiler 使用非官方的 AsyncGetCallTrace API。两者各有优缺点,但通常,JMX 及相关 API 被认为更安全,而 AsyncGetCallTrace 更精确。
OpenJDK 和 GraalVM 仅有一个内置分析器 Java Flight Recorder(JFR);它的工作原理与 async-profiler 大致相同,同样精确,但更稳定。
接下来,我将介绍这几个分析器及其历史。
该工具是 Netbeans 分析器的独立版本。从 2006 年的 Oracle JDK 6 到 JDK 8,每个 JDK 都包含 Java VisualVM 工具。该工具于 2008 年开源。后来,这个分析器更名为 VisualVM,Oracle JDK 9 不再包含它。根据 JetBrains 最近的一项调查,VisualVM 是最常用的开源分析器。需要的话,可以从这里下载。
它的用法很简单;只需要在 GUI 中为你想要分析的程序选择运行它的 JVM 并启动性能分析:
然后,你可以在一个简单的树形可视化中直接查看性能分析概要信息。也可以从命令行启动和停止抽样分析器:
visualvm --start-cpu-sampler <pid>
visualvm --stop-sampler <pid>
VisualVM 提供了易于使用的简单 UI,但需要注意,它使用了不太精确的 JVM API。
Async-profiler 是最常用的分析器之一,这不仅仅是因为它被嵌入到了许多其他工具中,如 IntelliJ Ultimate Profiler 和 AppIication Performance Monitors。你可以从项目的 GitHub 页面下载 async-profiler。它包含特定于平台的二进制文件,不支持 Windows。因此,我创建了 app-loader 项目,将所有 async-profiler 二进制文件封装到一个多平台二进制文件中,使得嵌入并使用这个分析器变得更容易。
你可以通过许多嵌入了 async-profiler 的工具使用它,或直接将其作为本机 Java 代理来使用。假设你下载了特定于平台的 libasyncProfiler.so,则只需在调用 Java 二进制文件时添加以下选项,即可分析 Java 应用程序的性能:
java
-agentpath:libasyncProfiler.so=start,event=cpu,file=flame.html,flamegraph …
这个调用告诉 async-profiler 生成一个火焰图。这是一种非常流行的可视化方式。
你也可以用它创建 JFR 文件:
java
-agentpath:libasyncProfiler.so=start,event=cpu,file=profile.jfr,jfr …
这个调用让你可以在众多查看器中查看性能分析概要文件。
以下是 async-profiler 的发展简史,感兴趣的可以了解一下。
2002 年 11 月,Sun(后来被 Oracle 收购)根据 JVM(TM)工具接口规范将 AsyncGetStackTrace API 添加到 JDK 中。新 API 使得从外部分析器获得精确的堆栈跟踪信息成为可能。Sun 引入这个 API 是为了给他们的 Sun Development Studio 添加一个完整的 Java 分析器。然而,两个月后,他们删除了该 API,原因未公开。但是,这个 API 仍然以 AsyncGetCallTrace 的形式保留在 JDK 中,直到今天一直存在,只是没有导出,所以比较难用。
几年后,人们偶然发现,这个 API 是一个不错的实现分析器的方法。2007 年,Jeremy Manson 在博文“使用 JVMTI/JVMPI、SIGPROF 和 AsyncGetCallTrace 进行性能分析”中,首次提到将 AsyncGetCallTrace 作为实现 Java 分析器的基础。从那时起,许多开源和闭源分析器就开始使用它。YourKit、JProfiler 和 honest-profiler 是其中几个比较有名的例子。Async-profiler 的开发始于 2016 年;它目前是使用 AsyncGetCallTrace 的最主要的开源分析器。
Async-profiler 的问题在于,它是基于一个非官方的内部 API。这个 API 没有经过官方 OpenJDK 测试套件的充分测试,随时都可能失效。尽管该 API 的广泛应用使得它已近乎标准化,但这仍然是一个风险。为了减轻这些风险,我目前正在编制一份 JDK 增强提案,在 OpenJDK 中增加一个官方的 AsyncGetCallTrace 版本;见 JEP 候选 435。
Async-profiler 的优势在于它的许多特性(如堆采样)、可嵌入性、对其他 JVM(如 OpenJ9)的支持,以及它小巧的代码库,这使得它的适应性非常好。要了解关于 async-profiler 的更多信息,可以查看 async-profiler 自述文件、async-profiler 维基以及 Krzysztof Ślusarski 提供的 async-profiler 实用手册。
JRockit 最初开发运行时分析器是为了内部使用,但它也越来越受应用程序开发人员的欢迎。后来,在 Oracle 收购了其开发公司之后,这些特性被集成到了 Oracle JDK 中。最终,Oracle 将该工具与 JDK11 一起开源,从那时起,它就成了 OpenJDK JVM 的内置分析工具,不再支持 OpenJ9 等其他 JVM 了。
它的工作原理与 async-profiler 类似,主要区别是它直接使用内部的 JVM API。该分析器的使用很简单,可以通过在 Java 二进制文件的调用中添加以下选项:
java \
-XX:+UnlockDiagnosticVMOptions \
-XX:+DebugNonSafepoints \ # improves precision
+FlightRecorder \ :
-XX:StartFlightRecording=filename=file.jfr \
arguments
或者使用 JDK 命令行工具jcmd
启动和禁用它:
jcmd PID JFR.start
jcmd PID JFR.dump filename=file.jfr
jcmd PID JFR.stop
JFR 捕获许多性能分析事件,从堆栈跟踪信息抽样到垃圾收集和类加载统计信息。JFR 事件网站上提供了所有事件的列表。我们甚至还可以添加自定义事件。
要了解更多关于这个工具的信息,可以阅读 JDK Flight Recorder、The Programmatic Way(来自 BellSoft)等博客的文章。
与 async-profiler 相比,JFR 的主要优势是它存在于所有平台的 OpenJDK 中,甚至在 Windows 上。此外,JFR 更稳定一些,记录的事件和信息也更多。JFR 有一个名为 JDK 任务控制的 GUI,它让你可以分析 JVM 性能并查看生成的 JFR 性能分析概要。
在使用我所介绍的分析器时,务请记住以下内容:它们本身也是软件,与大型项目 OpenJDK(或 OpenJ9)交织在一起,因此,它们也会遇到与它们所分析应用程序相同的典型问题:
测试可以更丰富,特别是底层 API,可以更好地测试一下;目前只有一个测试。(我正在努力)
测试可以做得更好:现有的测试甚至没有充分测试 API 是否适用于小样本。它只检查了最上面的帧,但忽略了返回的跟踪信息太短这个问题。我发现了这个问题并修复了测试用例。
缺乏自动化回归测试:缺乏测试还意味着,对当前项目中看似不相关部分的更改可能会对分析产生不利的影响,而又没有人注意到。
因此,对于分析器生成的性能分析概要,你要持保留态度。以下博文和演讲谈及了分析器的准确性问题:
分析器都是撒谎的霍比特人(https://www.youtube.com/watch?v=7IkHIqPeFjY&list=PLLLT4NxU7U1QYiqanOw48h0VUjlUvqCCv&index=3)
内联代码如何导致性能分析概要的混乱(http://psy-lob-saw.blogspot.com/2018/07/how-inlined-code-confusing-profiles.html)
为什么 JVM 现代分析器仍然有安全点偏见?(https://jpbempel.github.io/2022/06/22/debug-non-safepoints.html)
Java 性能分析 API 验证(https://mostlynerdless.de/blog/2023/03/14/validating-java-profiling-apis/)
此外,在极少数情况下,对应用程序进行性能分析还可能导致 JVM 崩溃。像 Jaroslav Bachorik 和我这样的 OpenJDK 开发人员正设法尽可能地修复底层分析 API 中存在的所有稳定性问题。在实践中,使用上面提到的任何一种分析器都是安全的,很少会引发崩溃。如果遇到问题,请联系分析程序开发人员或在相应的存储库中开一个 GitHub 问题。
现代基于抽样的 Java 分析器使得使用开源工具调查性能问题成为可能。你可以选择:
一个稍微有点不精确但易于使用并且提供了简单 UI 的工具(VisualVM)
一个内置的工具,提供包括 GC 信息在内的更多信息(JFR)
一个提供很多选项的工具,可以显示 C/C++ 代码的信息(async-profiler)
都试用一下,以便了解在下一次遇到性能问题时使用哪种工具。
原文链接:
https://www.infoq.com/articles/open-source-java-profilers/
相关阅读:
JEP 443:未命名模式和变量致力于提升 Java 代码的可读性 (https://www.infoq.cn/article/u9i2rWrCAQ4NsX7tyU8U)
JEP 444:JDK 21 中出现虚拟线程,开创并发新纪元 (https://www.infoq.cn/article/wxcjbFtvT7Twva0eeXTj)**
快速实现不打折扣的云原生 Java 应用](https://www.infoq.cn/article/R8sh9XHuojBsX9DpGYvJ)
声明:本文为 InfoQ 翻译,未经许可禁止转载。
点击底部阅读原文访问 InfoQ 官网,获取更多精彩内容!
红帽对 RHEL 下游造成毁灭性打击!停止公开企业版源代码,要挤占开源份额实现盈利?
AI之下没有秘密:网友诱骗ChatGPT激活 Windows 11,ChatGPT落入陷阱!
微信扫码关注该文公众号作者