共计 3471 个字符,预计需要花费 9 分钟才能阅读完成。
前言
工具的进化始终是人类生产力提高的标记,正当应用工具能大大提高咱们的工作效率,遇到问题时,正当应用工具更能放慢问题排查的进度。这也是我为什么十分喜爱 shell 的起因,它丰盛的命令行工具集加管道个性解决起文本数据集来真的精准而优雅,让人迷醉。
但很多时候文本的表现力十分无限,能够说匮乏,表白绝对值时,天然是无往不利,但在展现相对值时,就有些顾此失彼了,就更不用说多维数据了。
咱们用 shell 能够十分疾速地查问出文本内的累加值、最大值等,但一遇到两组值的相关性剖析时,就大刀阔斧了。这个时候,就须要应用另一种剖析工具 – 图了,如散点图就能很清晰地展现相关性。
明天就筹备介绍一种图, 火焰图 ,之前组内大神分享过它的应用方法,但我之后很久都没有用过,以至于对它没有什么深刻印象,最近排查咱们 Java 利用负载问题时试用了一下,这才对它的用处有了点心得。
介绍
引子
在排查性能问题时,咱们通常会把线程栈 dump 进去,而后应用
grep --no-group-separator -A 1 java.lang.Thread.State jstack.log | awk 'NR%2==0' | sort | uniq -c | sort -nr
相似的 shell 语句,查看大多数线程栈都在干什么。而由线程栈的呈现频率,来推断 JVM 内耗时最多的调用。
至于其原理,构想广场上有一个大屏幕在不停地播放各种广告。如果咱们随机对大屏幕拍照,次数多了,统计照片中各个广告呈现的频率,根本能够得出每个广告的播放时长占比了。
而咱们利用的资源就像大屏幕,每次调用就像是播放一次广告,统计 dump 出的线程栈呈现比例,也就根本能看出线程栈的耗时占比,尽管有误差,然而屡次统计下应该差不了多少。这也就是为什么有些家长每次进孩子房间都发现孩子在看零碎桌面后认为孩子平时喜爱对着桌面发愣的起因。:)
2444 at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1200)
1587 at sun.misc.Unsafe.park(Native Method)
795 at java.security.Provider.getService(Provider.java:1035)
293 at java.lang.Object.wait(Native Method)
292 at java.lang.Thread.sleep(Native Method)
73 at org.apache.logging.log4j.core.layout.TextEncoderHelper.copyDataToDestination(TextEncoderHelper.java:61)
71 at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
70 at java.lang.Class.forName0(Native Method)
54 at org.apache.logging.log4j.core.appender.rolling.RollingFileManager.checkRollover(RollingFileManager.java:217)
然而这样有些问题,首先写 shell 挺麻烦的,另外如果我想查看自栈顶第二个栈的最多调用,即便批改了 shell 命令,后果也不直观。
产生这个问题的次要起因是,咱们的线程栈是有调用关系的,即咱们须要思考线程栈的 调用链 和 呈现频率 两个维度,而繁多的文本体现这两种维度比拟艰难,所以,驰名性能剖析巨匠 brendan gregg 就提出了火焰图。
介绍
火焰图,因其形似火焰而得名,其开源代码地址:
https://github.com/brendangregg/FlameGraph
它是一种 svg 可交互式图形,咱们通过点击和鼠标指向能够展现出更多的信息。下图就是一个典型的火焰图,从构造上,它是由多个大小和色彩各异的方块形成,每个方块上都有字符,它们底部连贯在一块,组成火焰的基底,顶局部出许多”小火苗”。
当咱们点击方块时,图片会从咱们点击的方块为基底向上开展,而咱们鼠标指向方块时,会展现出方块的具体阐明。
个性
介绍火焰图的剖析前,咱们要首先阐明它的个性:
- 由底部到顶部能够追溯一个惟一的调用链,上面的方块是下面方块的父调用。
- 同一父调用的方块从左到右以字母序排列。
- 方块上的字符示意一个调用名称,括号内是火焰图指向的调用在火焰图中呈现的次数和这个方块占最底层方块的宽度百分比。
- 方块的色彩没有实际意义,相邻方块的色彩差只为了便于查看。
剖析
那么,给咱们一张火焰图,咱们怎么能看出零碎哪里有问题呢?
由上文中的火焰图个性个性,查看火焰图时,咱们最次要的关注点要放在方块的宽度上,因为宽度代表了调用栈在全局呈现的次数,次数代表着呈现频率,而频率也就能够阐明耗时。
然而察看火焰图底部或中部方块的宽度占比意义不大,如下面的火焰图,中部的 do_redirections 函数宽度是 24.87%,也就是说它耗用了整个利用近四分之一的工夫,然而真正耗费工夫的并不是 do_redirections 函数,而是 do_redirections 外部又调用的其余函数,而它的子调用分为了很多个,每个调用的耗时并没有异样。
咱们更应该关注的是火焰图顶部的一些“平顶山”,顶部阐明它没有子调用,方块宽阐明它耗时长,长时间 hang 住,或者被十分频率地调用,这种方块指向的调用才是性能问题的罪魁祸首。
找到了异样调用,间接优化它,或者再依据火焰图的调用链层层向下,找到咱们的业务代码进行优化,也就功败垂成。
利用场景
每种工具都有其适宜的利用场景,火焰图则适宜用在:
- 代码循环剖析:如果代码中有很大的循环或死循环代码,那么从火焰图的顶部或靠近项部的中央会有很显著的”平顶”,示意代码频繁地在某个线程栈高低切换。但须要留神的是,如果循环的总耗时不长,在火焰图上不会很显著。
- IO 瓶颈 / 锁剖析:在咱们的利用代码中,咱们的调用广泛都是同步的,也就是说在进行网络调用、文件 I/O 操作或未胜利取得锁时,线程会停留在某个调用上期待 I/O 响应或锁,如果这个期待十分耗时,会导致线程在某个调用上始终 hang 住,这在火焰图上体现得会十分清晰。与此绝对的是,咱们利用线程形成的火焰图无奈精确地表白 CPU 的耗费,因为利用线程内没有零碎的调用栈,在利用线程栈 hang 住时,CPU 可能去做其余事了,导致咱们看到耗时很长,而 CPU 却很闲。
- 火焰图倒置剖析全局代码:火焰图倒置有时也会很实用,如果咱们的代码 N 个不同的分支都调用某一办法,倒置后,所有栈顶雷同的调用被合并在一块,咱们就能看出这个办法的总耗时,也就很容易评估出优化这个办法的收益。
实现
既然火焰图这么弱小,那么咱们该怎么实现呢?
生成工具
brendan gregg 大神曾经把生成火焰图的办法用 perl 实现了,开源代码就在上文的 Github 仓库中,根目录下的 flamegraph.pl
文件就是可执行的 perl 文件了。
这个命令还能够传入各种参数,反对咱们批改火焰图的色彩、大小等。
但 flamegraph.pl 只能解决特定格局的文件,像:
a;b;c 12
a;d 3
b;c 3
z;d 5
a;c;e 3
后面是调用链,每个调用之间用 ; 隔开,每行前面的数字是调用栈呈现的次数。
如下面的数据,用 flamegraph.pl 生成的火焰图如下图:
数据筹备
至于咱们的 jstack
信息如何被解决成下面的格局,大神则为常见的 dump 格局都提供了工具,像 stackcollapse-perf.pl
能够解决 perf
命令的输入,stackcollapse-jstack.pl
解决 jstack 输入,stackcollapse-gdb.pl
解决 gdb 输入的栈等。
也能够用 shell 简略地实现一下 jstack
的解决形式:
grep -v -P '.+prio=d+ os_prio=d+' | grep -v -E 'locked <' | awk '{if ($0==""){print $0}else{printf"%s;",$0}}'| sort | uniq -c | awk'{a=$1;$1="";print $0,a}'
小结
火焰图总结完了,当前再遇到性能问题又多了一种应答形式。
做开发越久,越能感触失去工具的重要性,所以我筹备加一个专题来专门介绍我应用的各种工具。当然,这也就更须要我更多地理解、应用和总结新的工具了。
起源:https://zhenbianshu.github.io