打压时到了瓶颈,程序的吞吐不再上升。这时候,要找到瓶颈在哪里,是一件比较麻烦的事。我们需要加上一些打点,观察这些打点耗时的变化,来推测瓶颈所在。
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的资源。
优化方向可以集中在这两个地方了。