打压时到了瓶颈,程序的吞吐不再上升。这时候,要找到瓶颈在哪里,是一件比较麻烦的事。我们需要加上一些打点,观察这些打点耗时的变化,来推测瓶颈所在。

SJK - Swiss Java Knife (Java瑞士军刀)是一个JVM工具合集,提供了很多方便的功能。其中一个是: SJK可以采样一定时间段内JVM的Stack Trace,生成火焰图,帮助我们分析程序的性能瓶颈。

下载SJK

https://github.com/aragozin/jvm-tools 根据文档下载打好的Jar包。

上传SJK Jar包到被打压的机器上

mupload或者scp

找出被打压的进程id

jps -v

开始打压并启动SJK采样

先启动打压,运行命令

export pid=XXX
java -jar sjk.jar stcap -p ${pid} -o druid-${pid}.std -t 30s -i 50ms

开始一段持续30s,每50ms一次的采样。

采样完成后,运行

java -jar sjk.jar flame -f druid-${pid}.std -o druid-flame-${pid}.html

生成火焰图。

分析火焰图

简单介绍一下火焰图。火焰图上的小方块可以理解成一个方法调用。纵轴是调用栈,下面的方法调用了上面的方法。横轴是占用时间,即一个方法在采样中被采样到的次数占比,越宽说明占用的时间越长。横轴没有方向,在左或者在右没有含义。

可以想到,火焰图中下面的方法因为要等待上面的方法返回,所以肯定被采样到的次数更多,占用的面积也就更大。所以整体会呈现矩形,就像火焰一样。火焰图的颜色没有含义。

我们要找的,就是在最顶端,而且占用时间比较长的方法调用。这样的方法调用往往就是瓶颈。

正上方有all states,可以筛选线程的状态。右上角有threads,可以筛选线程。

筛选线程的状态

Java线程有六种状态, 主要关心四种状态:

  • RUNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING

个人理解:

  • CPU密集性的应用关注RUNABLE线程。主要花时间在CPU计算,往往Thread数量不会很多。
  • IO密集性的应用关注下面三种状态的线程:线程主要花时间在等待资源Ready。

打压一般不需要区别,全勾选上就可以。

筛选线程名称

线程池会给线程命名。我们一般关注部分线程。

如: 打压Spring时,一般只关注Tomcat的线程情况。如果打压完全没用到消息,消息队列的线程池往往就在等待,会干扰结果。右上角的Threads可以只选择Tomcat相关的线程,生成对应的火焰图。

例子

以Druid为例,简单看下如何利用火焰图找到瓶颈。

这是一张打压时Druid Historical 查询方法的火焰图。

看最上面的调用栈,找找出现频率高的最上层调用。

一个是,java.util.TreeMap.put,往下看,Druid是在查询VersionedIntervalTimeline。

一个是,sun.misc.Unsafe.park,在等待锁,往调用栈找,是在等ProritizedExecutorService的资源。

优化方向可以集中在这两个地方了。