上周一个偶然的机会听同事提到了Java FlameGraph,刚实验了一下,效果非常好。
一、什么是FlameGraph
直接看图说话。FlameGraph 是 SVG格式,矢量图,可以随意扩大缩小,看不清的信息可以放大看。图中,各种红橙黄色没有什么意义,仅仅做区分用;x轴横条宽度来度量时间指标,表明每个接口实际占用的CPU时间;y轴代表线程栈的层次,从最底下往上表示堆栈的层层调用。通过看图,可以发现哪个接口占用的CPU时间较多,从而优化;同时,可以发现调用关系。
Java火焰图的作者是Brendan Gregg,他的博客非常有意思,很多关于性能的分析。以下链接是对每个类别的火焰图的详细说明。
什么是Java Flame Graphs:Java Flame Graphs
On-CPU:CPU Flame Graphs
Off-CPU:Off-CPU Flame Graphs
Memory:Memory Leak (and Growth) Flame Graphs
Hot/Cold:Hot/Cold Flame Graphs
Differential:Differential Flame Graphs
关于火焰图的PPT(讲解得非常详细):Blazing Performance with Flame Graphs
二、如何生成
两个步骤:1. 需要java profiler生成trace文件 2. 将trace文件转换为svg格式的火焰图文件。
1. 需要java profiler生成trace文件
在使用Profiler对CPU进行采样时,根据CPU当前执行所处栈位置以及各个函数栈在总的采样次数所占比例就可以得出各个函数执行时的CPU占用比例。常用的是lightweight-java-profiler。还有其他的选择,比如honest-profiler,lightweight-java-profiler会从java虚拟机启动开始采样,而有时候我们需要在CPU飙高的时候开始,这时候honest-profiler提供的动态启停功能就有用武之地了。也有使用perf生成火焰图。(*perf 要研究一下)
下面以lightweight-java-profiler 举例
(1) 从github下载软件
(2) 编译 make all
(3) 生成的程序存放在build-64文件夹下面
(4)(可选)可以更改一些lightweight-java-profiler的一些选项,打开src/globals.h文件。在长时间采样时,可以适当地减少每秒采样次数,不然最终生成的文件会很大,分析起来比较麻烦。
// 每秒采样频率 static const int kNumInterrupts = 100; // Maximum number of stack traces线程栈个数 static const int kMaxStackTraces = 3000; // 采样栈深度 static const int kMaxFramesToCapture = 128;
kNumInterrupts: 每秒钟抽取样本的次数;
kMaxStackTraces: 线程栈的最大数量
kMaxFramesToCapture: 线程栈的深度
(5)运行Java程序
java -agentpath:path/to/liblagent.so ......
(6)java程序启动后会在当前目录生成一个traces.txt文件,但文件中只有一些说明信息。程序正常结束(不杀掉进程)后,才会写入具体采样信息。
2.将trace文件转换为svg格式的火焰图文件。
(1)从github下载FlameGraph
(2)转换
几个商用的profiler工具都存在上述问题。但是,Oracle Solaris studio利用的是jvmti的一个非标准接口AsyncGetCallTrace来实现,不存在上面问题,Jeremy Manson也利用该接口 实现了一个简单的profiler工具:lightweight-java-profiler。