Java性能分析工具Async-Profiler教程
什么是async-profiler
简介
async-profiler是一个低开销的 Java 采样分析器,它不会遇到安全点偏差问题。它使用HotSpot 特定的 API 来收集堆栈信息,追踪内存分配。该分析器可与 OpenJDK、Oracle JDK ,以及其他基于 HotSpot JVM 的 Java 运行时配合使用。
async-profiler 可以跟踪以下类型的事件:
- CPU周期
- 硬件和软件性能计数器,例如缓存未命中、分支未命中、页面错误、上下文切换等。
- Java 堆中的分配
- Contented lock尝试,包括 Java 对象监视器和 ReentrantLocks
下载地址
- Linux x64 (glibc): async-profiler-2.9-linux-x64.tar.gz
- Linux x64 (musl): async-profiler-2.9-linux-musl-x64.tar.gz
- Linux arm64: async-profiler-2.9-linux-arm64.tar.gz
- macOS x64/arm64: async-profiler-2.9-macos.zip
支持的平台
- Linux / x64 / x86 / arm64 / arm32 / ppc64le
- macOS / x64 / arm64
功能介绍
CPU profiling
在这个模式中,profiler收集stack traces的样本,其中包括Java methods,native calls, JVM代码和内核函数。
普遍的方法是接收 perf_events
生成的stack traces,然后将它们与 AsyncGetCallTrace
生成的调用堆栈进行匹配,以便生成 Java 和native code 的准确配置文件。此外,在 AsyncGetCallTrace
失败的某些极端情况下,async-profiler 提供了一种解决方法,可以恢复stack traces。
与另一种方式:直接将 perf_events
与 Java agent 结合使用,将地址转换为 Java method name 相比,该方法具有以下优点:
- 适用于较旧的 Java 版本,因为它不需要
-XX:+PreserveFramePointer
,该选项仅在 JDK 8u60 及更高版本中可用。 - 不会引入
-XX:+PreserveFramePointer
的性能开销,在极少数情况下,这种性能开销可能高达 10%。 - 不需要生成映射文件来将 Java 代码地址映射到方法名称。
- 与解释器框架一起使用。
- 不需要写出 perf.data 文件以在用户空间脚本中进行进一步处理。
ALLOCATION profiling
除了检测检测消耗 CPU 的代码,async-profiler可以配置为收集分配了最大堆内存的调用站点。
async-profiler 不使用字节码检测或昂贵的 DTrace 探针等侵入性技术,这些技术会对性能产生重大影响。它也不会影响逃逸分析或防止 JIT 优化(例如分配消除)。它仅测量实际的堆分配。
该profiler具有 TLAB 驱动的采样功能。它依赖于 HotSpot 特定的回调来接收两种类型的通知:
- 当在新创建的 TLAB 中分配对象时(火焰图中的水色框)
- 当对象分配在 TLAB 外部的慢速路径上时(棕色帧)。
这意味着不会计算每个分配,而只会计算每 N kB
的分配,其中 N 是 TLAB
的平均大小。这使得堆采样非常便宜并且适合生产。另一方面,收集的数据可能不完整,尽管在实践中它通常会反映最高的分配来源。
我们可以使用 --alloc
选项调整采样间隔。例如,--alloc 500k
将在平均分配 500 KB 空间后进行一次采样。但是,小于 TLAB
大小的间隔将不会生效。
支持的最低 JDK 版本是 7u40,其中出现了 TLAB 回调。
Installing Debug Symbols
在 JDK 11 之前,分配分析器需要 HotSpot 调试符号。 Oracle JDK 已将它们嵌入到 libjvm.so
中,但在 OpenJDK 版本中,它们通常在单独的包中提供。例如,要在 Debian / Ubuntu 上安装 OpenJDK 调试符号,请运行:
1 | apt install openjdk-8-dbg |
或者对于 OpenJDK 11:
1 | apt install openjdk-11-dbg |
gdb 工具可用于验证 libjvm 库的调试符号是否已正确安装。例如在 Linux 上:
1 | gdb $JAVA_HOME/lib/server/libjvm.so -ex 'info address UseG1GC' |
该命令的输出将包含Symbol "UseG1GC" is at 0xxxxx
或者No symbol "UseG1GC" in current context
。
Wall-clock profiling
-e wall
选项告诉 async-profiler 在每个给定时间段对所有线程进行同等采样,无论线程状态如何:正在运行、睡眠或阻塞。例如,这在分析应用程序启动时间时非常有用。
Wall-clock profiler在每线程模式下最有用:-t
。
举例:
1 | asprof -e wall -t -i 5ms -f result.html 8983 |
Java method profiling
-e ClassName.methodName
选项检测给定的 Java method,以便通过堆栈跟踪记录该方法的所有调用。
比如:-e java.util.Properties.getProperty
将分析调用 getProperty 方法的所有位置。
这种方式仅支持non-native Java methods。如果想要分析native method,需要使用hardware breakpoint event 事件,例如 -e Java_java_lang_Throwable_fillInStackTrace
请注意,如果您在运行时附加 async-profiler,non-native Java methods的第一次检测可能会导致所有已编译方法的去优化。后续检测仅刷新相关代码。如果将 async-profiler 作为agent附加,则不会发生大规模 CodeCache 刷新。
下面是一些对于分析有用的 native methods:
G1CollectedHeap::humongous_obj_allocate
:跟踪 G1 GC 的巨大分配JVM_StartThread
:跟踪新线程的创建Java_java_lang_ClassLoader_defineClass1
:跟踪类加载
如何安装
(1)确保环境变量JAVA_HOME
与GCC
正常,手动make
即可。
(1)直接下载二进制文件。
1 | cd async-profiler/ |
1 | Usage: ./profiler.sh [action] [options] <pid> |
基本用法
1 | jps |
结果如下:
1 | --- Execution profile --- |
注意!可以使用以下内容代替 pid (18006):
- 关键字
jps
,它将使用最近启动的 Java 进程。 jps
输出中显示的应用程序名称:例如Hello
。
当然,我们也可以只用单个命令完成统计(指定 -d
(持续时间)参数,在固定时间段内分析应用程序)。举例:
1 | sudo ./profiler.sh -d 30 18006 |
默认情况下,分析频率为 100Hz(每 10 毫秒 CPU 时间)。
以Agent的方式启动
如果你需要在 JVM 启动后立即分析某些代码,可以在命令行上附加 async-profiler 作为代理,而不是使用 asprof。例如:
1 | java -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,file=profile.html ... |
Agent library通过 JVMTI 参数接口进行配置。参数字符串的格式在源代码中描述。 asprof
实际上就是将命令行参数转换为该格式。比如,-e wall
转换为 event=wall
,-f profile.html
转换为 file=profile.html
等。然而,有些参数是由 asprof 直接处理的。例如。 -d 5
其实包含 3 个操作:
1 | (1)使用启动命令附加探查器代理 |
多个事件
使用async profiler,我们可以同时分析 CPU、allocations 和locks 。当然,也可以选择任何其他执行事件来代替 CPU:wall-clock、perf event、tracepoint、Java method 等。
唯一同时支持多个事件的输出格式是 JFR。记录将包含以下事件类型:
jdk.ExecutionSample
jdk.ObjectAllocationInNewTLAB
(alloc)jdk.ObjectAllocationOutsideTLAB
(alloc)jdk.JavaMonitorEnter
(lock)jdk.ThreadPark
(lock)
要开始一起分析cpu + allocations + locks ,命令如下:
1 | asprof -e cpu,alloc,lock -f profile.jfr ... |
或使用 --alloc
和 --lock
参数以及所需的阈值:
1 | asprof -e cpu --alloc 2m --lock 10ms -f profile.jfr ... |
同样,当作为代理启动探查器时:
1 | -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,alloc=2m,lock=10ms,file=profile.jfr |
火焰图可视化
async-profiler 提供开箱即用的 Flame Graph 支持。指定 -o Flamegraph
参数将分析结果转储为交互式 HTML 火焰图。此外,如果目标文件名以 .html 结尾,则会自动选择火焰图输出格式。
1 | jps |
Profiler Options
start
- 以半自动模式开始分析,即分析器将运行直到显式调用stop
命令。resume
- 启动或恢复之前已停止的分析会话。所有收集的数据仍然有效。分析选项不会在会话之间保留,应重新指定。stop
- 停止分析并打印报告。dump
- 转储收集的数据而不停止分析会话。check
- 检查指定的分析事件是否可用。status
- 打印分析状态:分析器是否处于活动状态以及持续时间。meminfo
- 打印已用内存统计信息。list
- 显示可用于目标进程(如果指定了 PID)或默认 JVM 的分析事件列表。-d N
- 分析持续时间(以秒为单位)。如果未给出start
、resume
、stop
或status
选项,profiler将运行指定的时间段,然后自动停止。-e event
- 分析事件:cpu、alloc、lock、cache-misses 等。使用 list 查看可用事件的完整列表。-o fmt
- 指定分析结束时转储哪些信息。 fmt 可以是以下选项之一:traces[=N]
- 转储call traces (最多 N 个 samples)flat[=N]
- 转储 flat profile(前N个热门方法);可以与traces
结合使用,例如traces=200,flat=200
jfr
- 以 Java Mission Control 可读的 Java Flight Recorder 格式转储事件collapsed
- 以 FlameGraph 脚本使用的格式转储折叠的调用跟踪。这是调用堆栈的集合,其中每一行都是以分号分隔的帧列表,后跟一个计数器flamegraph
- 生成 HTML 格式的火焰图。tree
- 生成 HTML 格式的调用树。--reverse
生成回溯视图。
还有很多,自行查阅相关文档…………
实战案例参考
1 | import java.util.ArrayList; |
CPU profiling
运行命令:
1 | ./profiler.sh -d 30 -f /tmp/19365.html 19365 |
结果
以另一种形式输出
1 | ./profiler.sh -d 30 -o tree -f /tmp/tmp.html 19685 |
Allocation profiling-1
1 | ./profiler.sh -d 30 -e alloc -f 19365-alloc.svg 19365 |
Allocation profiling-2
代码:
1 | import java.util.concurrent.ThreadLocalRandom; |
运行命令:
1 | ./profiler.sh start -e alloc 20172 |
结果:
1 | --- Execution profile --- |
生成火焰图命令:
1 | ./profiler.sh -d 60 -e alloc -f /tmp/20417.alloc.html 20417 |
结果:
Wall-clock profiling
1 | ./profiler.sh -e wall -t -i 5ms -f /tmp/19685.result.html 19685 |
放大main线程,发现与单独采样结果是一样的
methods profiling
采集methods
1 | ./profiler.sh -e HotCode.allocate 19685 |