几款java性能分析器的使用:
hprof
这是一个基于命令行的调试工具,基于JVMTI实现,可用于cpu使用分析,堆分配统计和竞争监视器分析。此外,它还可以导出完整的堆信息,所有监视器的状态和jvm里的线程信息。
hprof可以这样启动:
java -agentlib:hprof=cpu=samples,depth=100,interval=20,lineno=y,thread=y,file=out.hprof myclass
也可以这样:
java -Xrunhprof[:options] ToBeProfiledClass
分析器会在进程启动时运行,一直到程序退出或者进程收到一个SIGOUIT信号。
分析器会对程序运行栈进行采样,统计所有处于running状态的线程,累计这些活跃的堆栈的轨迹,这样累计数量最多的就很可能是cpu使用热点的堆栈。在上面的示例中,采样的栈深度为最大100层,每20ms采样一次,输出的结果文件为:out.hprof,结果内容包括了调用栈的列表和每个栈的累计调用计数。示例如下:
TRACE 301130: (thread=200001)
sun.security.ssl.SSLSocketFactoryImpl.createSocket(SSLSocketFactoryImpl.java:72)
javax.net.ssl.SSLContextSpi.getDefaultSocket(SSLContextSpi.java:143)
javax.net.ssl.SSLContextSpi.engineGetDefaultSSLParameters(SSLContextSpi.java:168)
javax.net.ssl.SSLContext.getDefaultSSLParameters(SSLContext.java:419)
com.netease.hmail.server.netty.NettyServer.prepare(NettyServer.java:503)
com.netease.hmail.server.launcher.HmailLauncher.prepare(HmailLauncher.java:588)
com.netease.hmail.server.launcher.HmailLauncher.main(HmailLauncher.java:415)
TRACE 301159: (thread=200001)
com.netease.hmail.server.launcher.HmailLauncher.prepare(HmailLauncher.java:591)
com.netease.hmail.server.launcher.HmailLauncher.main(HmailLauncher.java:415)
CPU SAMPLES BEGIN (total = 69) Sat Mar 16 11:17:11 2019
rank self accum count trace method
1 5.80% 5.80% 4 301179 java.net.SocketInputStream.socketRead0
2 4.35% 10.14% 3 300841 java.lang.ClassLoader$NativeLibrary.load
3 2.90% 13.04% 2 300901 java.io.BufferedInputStream.
4 1.45% 14.49% 1 300024 java.lang.System.arraycopy
这款分析器有个缺点导致它不是很靠谱:hprof在采用java的线程运行状态时,是通过state==JVMTI_THREAD_STATE_RUNNABLE来判断线程是否在占用cpu的,但是runnable状态实际上并不表示线程正在运行,它只表示线程是活跃的,可运行的,它是jvm意义上的runnable,跟操作系统内核调度器的"runnable"并不是同一个含义。所以hprof误以为这些线程在占用大量cpu而实际上cpu可能很空闲,所以根据hprof得到的结果可能不准确,这点要注意。
hprof还有另一个问题是它只在安全点(safepoings)时才进行采样,这样就不是在设置的时间间隔进行采样了,所以hprof是不太可靠的。
perf + perf-map-agent + FlameGraph
perf是linux系统级别的分析调试工具,它可以分析程序的各个调用链对cpu的使用进行采样,从而分析出哪个调用消耗的cpu最多。 perf的使用示例如下:
perf record -F 99 -p 13204 -g -- sleep 30
perf report -n --stdio
perf record命令将每秒采样99次(-F 99),目标进程id为13204(-p 13204),同时将采集程序调用栈(-g)。采样的结果可以解析成火焰图,这样看起来会更加直观,解析火焰图可以用FlameGraph,该工具可以从github上下载:
# git clone https://github.com/brendangregg/FlameGraph # or download it from github
# cd FlameGraph
# perf record -F 99 -a -g -- sleep 60
# perf script | ./stackcollapse-perf.pl > out.perf-folded
# ./flamegraph.pl out.perf-folded > perf-kernel.svg
perf的采样对于java程序有个问题:它不能识别java堆栈里的方法路径名称,只能以十六进制方式来表示,所以需要一个java符号转换表与perf进行结合来对java程序进行采样分析,这就是perf-map-agent所做的事。
perf-map-agent
这是一个JVMTI agent,它会为perf提供一份java符号表供转换用,另外,需要在jvm启动参数中加上: -XX:+PreserveFramePointer ,这样perf才能准确采样。
安装和使用perf-map-agent:
sudo bash
apt-get install cmake
export JAVA_HOME=/path-to-your-new-jdk8
cd /destination-for-perf-map-agent
git clone --depth=1 https://github.com/jvm-profiling-tools/perf-map-agent
cd perf-map-agent
cmake .
make
perf-map-agent如果要使用生成火焰图的脚本需要依赖FlameGraph:
git clone --depth=1 https://github.com/brendangregg/FlameGraph
编译后就可以使用bin目录下的工具进行调试分析了,工具说明如下:
create-java-perf-map.sh
takes a PID and options. It knows where to find libraries relative to thebin
directory.perf-java-top
takes a PID and additional options to pass toperf top
. Uses the agent to create a new/tmp/perf-
and then calls.map perf top
with the given options.perf-java-record-stack
takes a PID and additional options to pass toperf record
. Runsperf record -g -p
to collect performance data including stack traces. Afterwards it uses the agent to create a new/tmp/perf-
file..map perf-java-report-stack
calls firstperf-java-record-stack
and then runsperf report
to directly analyze the captured data. You can callperf report -i /tmp/perf-
again with any options after the script has exited to further analyze the data from the previous run..data perf-java-flames
collects data withperf-java-record-stack
and then creates a visualization using @brendangregg's FlameGraph tools. To get meaningful stacktraces spanning several JIT-compiled methods, you need to run your JVM with-XX:+PreserveFramePointer
(which is available starting from JDK8 update 60 build 19) as detailed in ag netflix blog entry.create-links-in
will install symbolic links to the above scripts into
.dtrace-java-record-stack
takes a PID. Runsdtrace
to collect performance data including stack traces. Afterwards it uses the agent to create a new/tmp/perf-
file..map dtrace-java-flames
collects data withdtrace-java-record-stack
and then creates a visualization using @brendangregg's FlameGraph tools. To get meaningful stacktraces spanning several JIT-compiled methods, you need to run your JVM with-XX:+PreserveFramePointer
(which is available starting from JDK8 update 60 build 19) as detailed in ag netflix blog entry.
编译后会生成 attach-main.jar和libperfmap.so两个文件,这是获取java程序运行时符号表的关键,而FlameGraph下面的jmaps脚本正是依赖于perf-map-agent来生成火焰图所需要的java符号表,所以perf-map-agent和FlameGraph可以说是相互依赖,查看一下两者的脚本就明白了。
下面的步骤可以生成火焰图:
git clone --depth=1 https://github.com/brendangregg/FlameGraph
sudo bash
perf record -F 49 -a -g -- sleep 30; ./FlameGraph/jmaps
perf script > out.stacks01
cat out.stacks01 | ./FlameGraph/stackcollapse-perf.pl | grep -v cpu_idle | \
./FlameGraph/flamegraph.pl --color=java --hash > out.stacks01.svg
也可以直接使用perf-java-map下面的脚本:
sudo ./bin/perf-java-flames 29685
注意:在使用这些脚时,需要修改一下脚本里所依赖的项目目录,如perf命令的名称和路径,FlameGraph的目录等。
async-profiler
同样是基于jvmti来开发的,下载地址如下:
git clone https://github.com/jvm-profiling-tools/async-profiler
需要编译一下:
cd async-profiler
make
编译后的脚本放在build目录下,进行分析的脚本是:profile.sh,启动采集命令:
./profiler.sh start $pid
停止采集:
./profiler.sh stop $pid
停止采集后会打印采集的结果。但是这样输出的结果比较简单,也没有产生火焰图,可以使用以下命令来执行采集任务并生成火焰图:
./profiler.sh -d 10 -o collapsed -f /tmp/collapsed.txt pid
./FlameGraph/flamegraph.pl --colors=java /tmp/collapsed.txt > flamegraph.svg
上面的含义是采集任务执行10秒种,并按collapsed格式输出到文件中,这样火焰图工具就可以通过解析这种格式来生成火焰图了。
【参考文献】
http://www.brendangregg.com/blog/2014-06-09/java-cpu-sampling-using-hprof.html
https://docs.oracle.com/javase/7/docs/technotes/samples/hprof.html
http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html