1. 概述
市面上大多数 Java 采样式性能分析工具(Profiler)基于 JVM Tool Interface (JVMTI) 实现,它们通常在安全点(safepoint) 采集线程栈。这就带来一个经典问题——safepoint 偏差:某些本该被采样的耗时操作,可能因为线程未到达安全点而被漏掉,导致分析结果失真。
要全面了解应用的真实性能瓶颈,我们需要一种不依赖安全点的采样分析器,能随时抓取调用栈,彻底规避 safepoint 偏差。
本文将深入介绍 async-profiler,一个能有效解决上述问题的高性能采样分析工具,并演示其多种分析能力。
2. async-profiler 简介
async-profiler 是一款适用于所有基于 HotSpot JVM 的 JDK 的采样分析器。它具备低开销的特点,且不依赖 JVMTI。
✅ 核心优势:它通过 HotSpot JVM 提供的 AsyncGetCallTrace
API 来采集 Java 代码的调用栈,同时利用 Linux 的 perf_events
机制采集 native 代码路径。这种双轨机制确保了无论是 Java 层还是 native 层的调用栈都能被准确捕获和匹配,从而提供全面、无偏差的性能视图。
❌ 与传统工具相比,它从根本上规避了因等待安全点导致的采样偏差。
3. 环境准备
3.1 安装
首先,根据你的操作系统平台,从 async-profiler 的 GitHub 发布页 下载最新版本。目前主要支持 Linux 和 macOS。
下载解压后,先验证是否正常工作:
$ ./profiler.sh --version
Async-profiler 1.7.1 built on May 14 2020
Copyright 2016-2020 Andrei Pangin
强烈建议提前查看所有可用命令选项,做到心中有数:
$ ./profiler.sh
Usage: ./profiler.sh [action] [options]
Actions:
start start profiling and return immediately
resume resume profiling without resetting collected data
stop stop profiling
check check if the specified profiling event is available
status print profiling status
list list profiling events supported by the target JVM
collect collect profile for the specified period of time
and then stop (default action)
Options:
-e event profiling event: cpu|alloc|lock|cache-misses etc.
-d duration run profiling for seconds
-f filename dump output to
-i interval sampling interval in nanoseconds
-j jstackdepth maximum Java stack depth
-b bufsize frame buffer size
-t profile different threads separately
-s simple class names instead of FQN
-g print method signatures
-a annotate Java method names
-o fmt output format: summary|traces|flat|collapsed|svg|tree|jfr
-I include output only stack traces containing the specified pattern
-X exclude exclude stack traces with the specified pattern
-v, --version display version string
--title string SVG title
--width px SVG width
--height px SVG frame height
--minwidth px skip frames smaller than px
--reverse generate stack-reversed FlameGraph / Call tree
--all-kernel only include kernel-mode events
--all-user only include user-mode events
--cstack mode how to traverse C stack: fp|lbr|no
is a numeric process ID of the target JVM
or 'jps' keyword to find running JVM automatically
这些选项在后续分析中会频繁用到。
3.2 内核配置(仅限 Linux)
在 Linux 上使用 async-profiler 时,需要调整内核参数,以允许普通用户通过 perf_events
采集调用栈:
降低 perf 事件权限限制:将
perf_event_paranoid
设为 1,允许用户态程序收集性能数据。$ sudo sh -c 'echo 1 >/proc/sys/kernel/perf_event_paranoid'
解除内核地址隐藏:将
kptr_restrict
设为 0,避免内核地址被隐藏,影响 native 栈的解析。$ sudo sh -c 'echo 0 >/proc/sys/kernel/kptr_restrict'
⚠️ macOS 平台无需此步骤,async-profiler 可直接运行。
准备好环境后,启动待分析的应用程序,强烈建议添加以下 JVM 参数以获得更精确的结果:
$ java -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -jar /path/to/your/app.jar
这两个参数能解锁诊断选项并保留非安全点的调试信息,对分析精度至关重要。
现在,我们可以开始使用 async-profiler 进行各种类型的性能分析了。
4. CPU 性能分析
CPU 分析模式会周期性地采集应用的调用栈样本,涵盖 Java 方法、JVM 代码、native 类库以及内核函数。
使用以下命令对目标进程(PID: 66959)进行 30 秒的 CPU 分析,并以摘要格式输出:
$ ./profiler.sh -e cpu -d 30 -o summary 66959
Started [cpu] profiling
--- Execution profile ---
Total samples : 28
Frame buffer usage : 0.069%
-
-e cpu
:指定分析事件为 CPU。 -
-d 30
:分析持续 30 秒。 -
-o summary
:输出格式为摘要(summary),也可选traces
,flat
等。
生成 HTML 格式的报告,便于交互式查看:
$ ./profiler.sh -e cpu -d 30 -f cpu_profile.html 66959
HTML 报告支持展开/折叠和搜索,非常方便。
✅ async-profiler 原生支持火焰图(Flame Graph),这是分析性能瓶颈的利器。生成 SVG 格式的火焰图:
$ ./profiler.sh -e cpu -d 30 -f cpu_profile.svg 66959
火焰图中:
- 绿色:Java 代码路径
- 黄色:C++ 代码路径
- 红色:系统代码路径
一目了然地看出 CPU 时间主要消耗在哪些函数上。
5. 内存分配分析
内存分配分析无需字节码增强等侵入式技术,开销极低。
async-profiler 利用 JVM 的 TLAB(Thread Local Allocation Buffer) 机制,对超出 TLAB 平均大小的堆内存分配进行采样。
使用 alloc
事件启动分配分析:
$ ./profiler.sh -e alloc -d 30 -f alloc_profile.svg 66255
从火焰图中可以清晰地看到 Object.clone()
操作占用了大量的内存分配,这种问题在纯代码审查时很难被发现。
6. 墙钟时间(Wall-Clock)分析
墙钟时间分析不关心线程状态,无论线程是运行、睡眠还是阻塞,都会被采样。这对于分析应用启动慢、I/O 等待、锁竞争等问题非常有用。
使用 wall
事件进行分析,并建议开启 -t
选项以按线程分别显示:
$ ./profiler.sh -e wall -t -d 30 -f wall_clock_profile.svg 66959
此外,可以使用 list
命令查看目标 JVM 支持的所有分析事件:
$ ./profiler.sh list 66959
Basic events:
cpu
alloc
lock
wall
itimer
Java method calls:
ClassName.methodName
7. 在 IntelliJ IDEA 中使用 async-profiler
IntelliJ IDEA 内置了对 async-profiler 的集成,可以直接在 IDE 内进行性能分析。
7.1 配置 Profiler
在 Settings/Preferences > Build, Execution, Deployment > Profiler
中配置 async-profiler 路径。
IDEA 提供了 CPU Profiler 和 Allocation Profiler 等预设配置,开箱即用。
对于特殊需求,可以复制模板并编辑 Agent options
进行自定义。
7.2 使用 IDEA 进行分析
有多种方式启动分析:
分析完成后,IDE 底部的 Profiler 工具窗口会展示结果。
结果支持按线程查看,并以火焰图、调用树、方法列表等多种格式呈现。
也可以通过 View > Tool Windows > Profiler
打开该窗口。
8. 总结
本文系统地介绍了 async-profiler 的核心原理和使用方法。
- 规避偏差:其基于
AsyncGetCallTrace
和perf_events
的设计,有效解决了传统采样器的 safepoint 偏差问题。 - 多维度分析:支持 CPU、内存分配、墙钟时间等多种分析模式,覆盖了性能排查的主要场景。
- 便捷集成:与 IntelliJ IDEA 的无缝集成,让开发过程中的性能分析变得简单粗暴。
无论是线上问题排查还是日常性能优化,async-profiler 都是一个值得掌握的利器。记住在 Linux 上正确配置内核参数,并使用推荐的 JVM 启动参数,以获得最准确的分析结果。