了解java程序中CPU占用情况对于java程序开发,尤其是后台服务开发来说是一件必要且重要的事情。在分析高并发下服务性能压力时,需要了解程序中长时间占用CPU的程序块。这需要我们使用分析器(profiler)在java进程运行的过程中定时抽样分析。
lightweight java profiler是一种针对java的轻量级分析器,使用步骤简单,共分为两步:
获得源码,make all,在build-32/build-64目录下生存liblagent.so库文件
执行java程序,并将此库文件作为-agentpath加入java虚拟机选项中去
运行一段时间后退出java程序,java程序根目录下会生成跟踪文件traces.txt,里面记录了定时抽样获得的java堆栈信息。
分析器默认参数:
- // Number of times per second that we profile
- static const int kNumInterrupts = 100;
-
- // Maximum number of stack traces
- static const int kMaxStackTraces = 3000;
-
- // Maximum number of frames to store from the stack traces sampled.
- static const int kMaxFramesToCapture = 128;
-
- // Location where the data are dumped.
- static const char kDefaultOutFile[] = "traces.txt";
kNumInterrupts: 每秒钟抽取样本的次数;
kMaxStackTraces: 抽取栈的最高数量;超过此数量的栈信息将不再被记录,但重复的栈仍然会被记录;
kMaxFramesToCapture: 获取帧最大数量;意义不明,我猜测可能是栈信息层数的上限;
traces.txt中的信息如下所示:
- 1 12 java.util.concurrent.ArrayBlockingQueue.insert(ArrayBlockingQueue.java:153)
- java.util.concurrent.ArrayBlockingQueue.offer(ArrayBlockingQueue.java:303)
- org.apache.flume.channel.file.LogFile$RandomReader.checkIn(LogFile.java:531)
- org.apache.flume.channel.file.LogFile$RandomReader.get(LogFile.java:496)
- org.apache.flume.channel.file.Log.get(Log.java:606)
- org.apache.flume.channel.file.FileChannel$FileBackedTransaction.doTake(FileChannel.java:523)
- org.apache.flume.channel.BasicTransactionSemantics.take(BasicTransactionSemantics.java:113)
- org.apache.flume.channel.BasicChannelSemantics.take(BasicChannelSemantics.java:95)
- org.apache.flume.sink.hdfs.HDFSEventSink.process(HDFSEventSink.java:374)
- org.apache.flume.sink.DefaultSinkProcessor.process(DefaultSinkProcessor.java:68)
- org.apache.flume.SinkRunner$PollingRunner.run(SinkRunner.java:147)
- java.lang.Thread.run(Thread.java:724)
-
- 1 4 sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:120)
- sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java:150)
- java.net.URLClassLoader.getResourceAsStream(URLClassLoader.java:233)
- javax.xml.parsers.SecuritySupport$4.run(SecuritySupport.java:94)
可见记录的都是java程序运行过程中的堆栈调用信息。然而这样的结果可读性比较差,我们可以使用java flame graph来将其转化为可读结果。
java flame graph是个非常讨巧的设计。它本身不支持堆栈抽样,但是它能将抽样结果转化为特别容易理解的形式,及“火焰图”。先看一下效果:
里面各种红橙黄色没有什么意义,只是做区分用的。从最底下往上表示堆栈的层层调用。横向表明每个接口实际占用的CPU时间。我这个火焰图测试的是flume(不是flame graph)的测试结果。可以看出有两块比较占用CPU时间的地方,分别是对磁盘的读操作和写操作。是不是很直观啊。
flame graph支持多种抽样器,这里我们先了解一下java flame graph的使用方法,这也是一种比较简单的使用方式。
在上述lightweight java profiler中,我们提到抽样器生成的结果是一个很难看懂的traces.txt,而在使用java flame graph的时候,只要以下命令:
./stackcollapse-ljp.awk < ../traces.txt | ./flamegraph.pl > ../traces.svg
然后会生成一个traces.svg,调用浏览器打开就是上面那个图的样子了。这是个矢量图,所以可以随意扩大缩小;在全图下看不清的底层堆栈信息,放大到一定程度后都能看的清清楚楚。