背景
容器化越来越普及,同时再加上 Java 程序运行在 JVM 中,使得我们使用 perf 来获取 Java 代码调用栈和符号表变得比较麻烦,因此我们需要结合 JVM 的一些新特性以及 perf-agent-map 来制作容器化 Java 进程的火焰图。
目标
Profiling for CPU bottlenecks
Visualizing and exploring stack traces using flame graphs
相关知识点
JVM and Linux Performance Sources
![]()
安全点偏差(Safepoint Bias)
Samples are captured only at safepoints
Research 《Evaluating The Accuracy of Java Profilers》 by Mytkowicz, Diwan, Hauswirth, Sweeney shows wild variety of results between profilers due to safepoint bias
Additionally, capturing a full stack trace for all threads is quite expensive (think Spring,调用栈又臭又长)
在Java虚拟机(JVM)的运行时环境中,安全点(Safepoint)是一个程序执行时的一个特殊位置,其中线程可以安全地停顿,而不会导致数据不一致或不安全的状态。安全点通常用于执行垃圾回收、线程同步和其他需要全局一致性的操作。在Java的性能分析和调优中,安全点偏差是一个重要的概念。
安全点偏差(Safepoint Bias)是指线程在等待安全点的时候,可能会经历一些额外的等待时间。当线程想要停在安全点时,它可能会遇到某些阻塞操作,例如正在执行一个同步块、持有锁、执行本地方法等。这些操作可能会导致线程无法立即停在安全点,从而导致安全点偏差。
在Java性能分析中,特别是在使用性能分析工具进行剖析(profiling)时,安全点偏差可能会影响性能分析的准确性。性能分析工具通常在安全点处采样线程的状态和堆栈信息,以获取程序的性能数据。如果线程由于安全点偏差而无法及时停在安全点,性能分析工具可能会错过线程的快照,从而导致分析结果的偏差。
准备
安装 perf
yum install perf -y #Red Hatapt install linux-tools-common #Debian
JVM 参数
从系统层面你只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。
perf_events 实际上已经支持了 JIT,但还需要一个 /tmp/perf-PID.map 文件,来进行符号翻译。当然,开源项目 perf-map-agent 可以帮你生成这个符号表。此外,为了生成全部调用栈,你还需要开启 JDK 的选项 -XX:+PreserveFramePointer。
Java 版本 >= Java 8u60
准备 perf-map-agent
git clone https://github.com/jvm-profiling-tools/perf-map-agent.git cd perf-map-agent yum install make # 如果没有安装 cmake . make
查找 Java 的 pid
docker ps | grep java | grep -v pause3e4ea0053140 490a62cde301 "/boot/entrypoint.sh…" 3 weeks ago Up 3 weeks docker inspect --format {{.State.Pid}} 3e4ea0053140397400 pstree -p 397400sudo(397400)───bash(397611)───java(397612)─...
通过 perf-map-agent 获取调用栈信息
【重点】在容器中获取调用栈信息
docker cp perf-map-agent/. 3e4ea0053140:/home/perf-map-agent docker exec -it 3e4ea0053140 /bin/bash ps auxUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.0 0.0 110816 4308 ? Ss Nov01 0:00 sudo -u hdfs /bin/bash -c ...hdfs 86 0.0 0.0 12860 1752 ? S Nov01 0:00 /bin/bash -c ...hdfs 87 0.2 0.3 11385912 1023520 ? Sl Nov01 61:14 java ...#注意这里的 pid 要用容器内看到的,这里是 87 ./perf-map-agent/bin/create-java-perf-map.sh 87 ll /tmp/perf-87.pid #检查是否生成 exit docker cp 3e4ea0053140:/tmp/perf-87.map /tmp/perf-397612.map #这里要 cp 成外面主机上看到的 pid
制作火焰图
执行 perf
![]()
sudo perf record -g -p 397612 -- sleep 30[ perf record: Woken up 1 times to write data ][ perf record: Captured and wrote 0.199 MB perf.data (758 samples) ]
当 CPU 比较繁忙的时候,需要使用参数 -F,用于减少 sampling 的数量
生成火焰图
可以看到很多符号都是地址形式展示
perf report Samples: 758 of event 'cycles:ppp', Event count (approx.): 163812990 Children Self Command Shared Object Symbol+ 91.84% 0.00% xxx xxx [.] 0x000000000046bf21+ 58.58% 0.00% xxx xxx [.] 0x0000000000846a6d...
【关键点】使用指定符号表
#创建临时目录mkdir /tmp/java#挂载容器进程符号表bindfs /proc/397612/root /tmp/java#使用指定的符号表解析perf report --symfs /tmp/license --kallsyms /proc/kallsymsSamples: 758 of event 'cycles:ppp', Event count (approx.): 163812990#使用容器符号表处理perf script --symfs /tmp/java --kallsyms /proc/kallsyms > out.perf #生成火焰图FlameGraph/stackcollapse-perf.pl out.perf > out.folded FlameGraph/flamegraph.pl out.folded > 397612.svg #处理完之后,记住要 umountumount /tmp/java/
perf report --symfs /tmp/license --kallsyms /proc/kallsyms
perf report --symfs /tmp/license --kallsyms /proc/kallsyms
增加 --kallsyms /proc/kallsyms 可以看到内核调用符号表
总结
per-pid.map
![]()
perf-map-agent
his is a JVMTI agent that attaches on demand to the Java process
Additional options include dottedclass, unfoldall, sourcepos
Consider -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints for more accurate inline info
【注意】采样和获取符号表中间有时间差,会导致有些调用栈无法解析。原因是 Java 调用栈太多了,所以执行不同代码的时候,perf-pid.map 会变。
恭喜你,大功告成!
参考
Java Flame Graphs
容器化越来越普及,同时再加上 Java 程序运行在 JVM 中,使得我们使用 perf 来获取 Java 代码调用栈和符号表变得比较麻烦,因此我们需要结合 JVM 的一些新特性以及 perf-agent-map 来制作容器化 Java 进程的火焰图。
目标
Profiling for CPU bottlenecks
Visualizing and exploring stack traces using flame graphs
相关知识点
JVM and Linux Performance Sources
安全点偏差(Safepoint Bias)
Samples are captured only at safepoints
Research 《Evaluating The Accuracy of Java Profilers》 by Mytkowicz, Diwan, Hauswirth, Sweeney shows wild variety of results between profilers due to safepoint bias
Additionally, capturing a full stack trace for all threads is quite expensive (think Spring,调用栈又臭又长)
在Java虚拟机(JVM)的运行时环境中,安全点(Safepoint)是一个程序执行时的一个特殊位置,其中线程可以安全地停顿,而不会导致数据不一致或不安全的状态。安全点通常用于执行垃圾回收、线程同步和其他需要全局一致性的操作。在Java的性能分析和调优中,安全点偏差是一个重要的概念。
安全点偏差(Safepoint Bias)是指线程在等待安全点的时候,可能会经历一些额外的等待时间。当线程想要停在安全点时,它可能会遇到某些阻塞操作,例如正在执行一个同步块、持有锁、执行本地方法等。这些操作可能会导致线程无法立即停在安全点,从而导致安全点偏差。
在Java性能分析中,特别是在使用性能分析工具进行剖析(profiling)时,安全点偏差可能会影响性能分析的准确性。性能分析工具通常在安全点处采样线程的状态和堆栈信息,以获取程序的性能数据。如果线程由于安全点偏差而无法及时停在安全点,性能分析工具可能会错过线程的快照,从而导致分析结果的偏差。
准备
安装 perf
yum install perf -y #Red Hatapt install linux-tools-common #Debian
JVM 参数
从系统层面你只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。
perf_events 实际上已经支持了 JIT,但还需要一个 /tmp/perf-PID.map 文件,来进行符号翻译。当然,开源项目 perf-map-agent 可以帮你生成这个符号表。此外,为了生成全部调用栈,你还需要开启 JDK 的选项 -XX:+PreserveFramePointer。
Java 版本 >= Java 8u60
准备 perf-map-agent
git clone https://github.com/jvm-profiling-tools/perf-map-agent.git cd perf-map-agent yum install make # 如果没有安装 cmake . make
查找 Java 的 pid
docker ps | grep java | grep -v pause3e4ea0053140 490a62cde301 "/boot/entrypoint.sh…" 3 weeks ago Up 3 weeks docker inspect --format {{.State.Pid}} 3e4ea0053140397400 pstree -p 397400sudo(397400)───bash(397611)───java(397612)─...
通过 perf-map-agent 获取调用栈信息
【重点】在容器中获取调用栈信息
docker cp perf-map-agent/. 3e4ea0053140:/home/perf-map-agent docker exec -it 3e4ea0053140 /bin/bash ps auxUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.0 0.0 110816 4308 ? Ss Nov01 0:00 sudo -u hdfs /bin/bash -c ...hdfs 86 0.0 0.0 12860 1752 ? S Nov01 0:00 /bin/bash -c ...hdfs 87 0.2 0.3 11385912 1023520 ? Sl Nov01 61:14 java ...#注意这里的 pid 要用容器内看到的,这里是 87 ./perf-map-agent/bin/create-java-perf-map.sh 87 ll /tmp/perf-87.pid #检查是否生成 exit docker cp 3e4ea0053140:/tmp/perf-87.map /tmp/perf-397612.map #这里要 cp 成外面主机上看到的 pid
制作火焰图
执行 perf
sudo perf record -g -p 397612 -- sleep 30[ perf record: Woken up 1 times to write data ][ perf record: Captured and wrote 0.199 MB perf.data (758 samples) ]
当 CPU 比较繁忙的时候,需要使用参数 -F,用于减少 sampling 的数量
生成火焰图
可以看到很多符号都是地址形式展示
perf report Samples: 758 of event 'cycles:ppp', Event count (approx.): 163812990 Children Self Command Shared Object Symbol+ 91.84% 0.00% xxx xxx [.] 0x000000000046bf21+ 58.58% 0.00% xxx xxx [.] 0x0000000000846a6d...
【关键点】使用指定符号表
#创建临时目录mkdir /tmp/java#挂载容器进程符号表bindfs /proc/397612/root /tmp/java#使用指定的符号表解析perf report --symfs /tmp/license --kallsyms /proc/kallsymsSamples: 758 of event 'cycles:ppp', Event count (approx.): 163812990#使用容器符号表处理perf script --symfs /tmp/java --kallsyms /proc/kallsyms > out.perf #生成火焰图FlameGraph/stackcollapse-perf.pl out.perf > out.folded FlameGraph/flamegraph.pl out.folded > 397612.svg #处理完之后,记住要 umountumount /tmp/java/
perf report --symfs /tmp/license --kallsyms /proc/kallsyms
perf report --symfs /tmp/license --kallsyms /proc/kallsyms
增加 --kallsyms /proc/kallsyms 可以看到内核调用符号表
总结
per-pid.map
perf-map-agent
his is a JVMTI agent that attaches on demand to the Java process
Additional options include dottedclass, unfoldall, sourcepos
Consider -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints for more accurate inline info
【注意】采样和获取符号表中间有时间差,会导致有些调用栈无法解析。原因是 Java 调用栈太多了,所以执行不同代码的时候,perf-pid.map 会变。
恭喜你,大功告成!
参考
Java Flame Graphs