来源:https://zhenbianshu.github.io前言工具的进化一直是人类生产力进步的标志。合理使用工具可以大大提高我们的工作效率。遇到问题时,合理使用工具可以加快故障排除进度。这也是我非常喜欢shell的原因。其丰富的命令行工具和管道功能在处理文本数据集时确实准确而优雅,令人着迷。但在很多时候,文字的表现力是非常有限的,可以说是稀缺的。在表达绝对值的时候,自然是无敌的,但是在表达相对值的时候,就有点捉襟见肘了,更何况是多维数据。我们可以用shell非常快速的查询文本中的累计值、最大值等,但是遇到两组值的相关性分析时,我们就束手无策了。这时候就需要用到另一种分析工具——图形,比如散点图,它可以清楚地显示相关性。今天要介绍一个图,火焰图。群里的高手之前有分享过使用方法,但是我好久没用了,印象不是很深。最近,我在排查我们的Java应用程序加载问题时尝试了它。过了一会儿,我对它的用途有了一些了解。介绍在排查性能问题时,我们通常会转储线程堆栈,然后使用grep--no-group-separator-A1java.lang.Thread.Statejstack.log|awk'NR%2==0'|排序|uniq-c|sort-nr类似的shell语句来查看大多数线程堆栈在做什么。JVM中最耗时的调用是从线程堆栈的频率推断出来的。至于它的原理,想象一下广场上有一个大屏幕不断播放着各种广告。如果我们随机拍摄大屏照片,次数太多,统计照片中每个广告出现的频率,基本上可以得出每个广告播放时间的比例。我们应用的资源就像一个大屏幕,每一次调用都像是在播放广告。通过统计dump中线程栈的占比,我们基本可以看出线程栈的耗时占比。虽然有误差,但很多时候的数据应该相差不大。这也是为什么有的家长发现,每次进入孩子房间,孩子都认为孩子喜欢在看完系统桌面后,盯着桌面发呆。:)2444atorg.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1200)1587atsun.misc.Unsafe.park(NativeMethod)795atjava.security.Provider.getService(Provider.java:1035)293atjava.lang.Object.wait(NativeMethod)292atjava.lang.Thread.sleep(NativeMethod)73atorg.apache.logging.log4j.core.layout.TextEncoderHelper.copyDataToDestination(TextEncoderHelper.java:61)71atsun.nio.ch.EPollArrayWrapper.epollWait(NativeMethod)70atjava.lang.Class.forName0(NativeMethod)54atorg.apache.logging.log4j.core.appender.rolling.RollingFileManager.checkRollover(RollingFileManager.java:217)但是这种方式存在一些问题。第一,写shell很麻烦。另外,如果我想查看栈顶从第二个栈调用的次数最多,即使修改shell命令,结果也不直观。出现这个问题的主要原因是我们的线程栈有一个调用关系,也就是我们需要考虑线程栈的调用链和出现频率两个维度,而这两个维度很难用一个维度来表达单一文本,因此著名的性能分析大师brendangregg提出了火焰图。介绍一下火焰图,因其形状像火焰而得名,其开源代码地址:https://github.com/brendangre...是一个svg交互式图形,我们可以通过鼠标点击显示更多信息。下图是一个典型的火焰图。从结构上看,它是由多个不同大小和颜色的正方形组成的。每个方块上都有字符。它们在底部相连,形成火焰的底部。“小火苗”。当我们点击方块时,图片会从我们点击的方块底部展开,当我们将鼠标指向方块时,会显示方块的详细描述。特点介绍在分析火焰图之前,首先要说明一下它的特点:可以自下而上追踪到一条唯一的调用链,下块是上块的父调用。同一父调用的方块按字母顺序从左到右排序。方块上的字符代表一个调用名,括号中是火焰图指向的调用在火焰图中出现的次数和这个方块占底部方块宽度的百分比。方块的颜色没有实际意义,相邻方块之间的色差只是为了方便观看。分析那么,给我们一张火焰图,我们如何看出系统哪里出了问题呢?从上面火焰图的特点来看,在查看火焰图的时候,我们主要关注的应该是盒子的宽度,因为宽度代表了调用栈全局出现的次数,而数字代表了出现的频率,而且频率也是耗时可以解释的。但是,观察火焰图底部或中间的正方形的宽度比例是没有意义的。比如上面的火焰图中,中间的do_redirections函数的宽度是24.87%,也就是说它占用了整个应用近四分之一的时间,但真正耗时的并不是do_redirections函数,但是在do_redirections内部调用的其他函数,它的子调用分为很多,每次调用的耗时都没有异常。我们更应该注意的是火焰图顶部的一些“平顶山”。顶部表示没有子调用,方块的宽度表示耗时长,挂了时间长,或者调用非常频繁。此类方块指向的调用是性能问题的罪魁祸首。找到异常调用,直接优化,或者找到我们的业务代码,根据火焰图的调用链进行优化,就大功告成了。应用场景每个工具都有自己适合的应用场景,火焰图适合用在:topof有明显的“平顶”,说明代码在某个线程栈上频繁上下切换。但需要注意的是,如果总循环时间不长,在火焰图上不会很明显。IO瓶颈/锁分析:在我们的应用代码中,我们的调用一般都是同步的,也就是说当进行网络调用、文件I/O操作或者获取锁不成功时,线程会停留在某个调用上等待I/O响应或者锁,如果这个等待很耗时,会导致线程挂在某个调用上,在火焰图上会很明显。相比之下,我们的应用线程形成的火焰图并不能准确表达CPU消耗,因为应用线程中没有系统调用栈。当应用程序线程栈挂掉的时候,CPU可能会做其他事情,导致我们看到耗时很长,CPU空闲。全局代码的Flamegraphinversion分析:Flamegraphinversion有时候很实用。如果我们代码的N个不同分支都调用了某个方法,倒置之后,所有栈顶相同的调用合并在一起,我们可以看到这个方法的总耗时,很容易评估优化这个方法的收益。实现既然火焰图这么强大,那么我们该如何实现呢?生成工具brendangregg实现了用perl生成火焰图的方法。开源代码在上面的Github仓库中。根目录中的flamegraph.pl文件是一个可执行的perl文件。该命令还可以传入各种参数,允许我们修改火焰图的颜色、大小等。但是flamegraph.pl只能处理特定格式的文件,比如:a;b;c12a;d3b;c3z;d5a;c;e3前面有一个调用链,每个调用之间用;隔开。该行后面的数字是调用堆栈出现的次数。如上数据,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'锁定<'|awk'{if($0==""){print$0}else{printf"%s;",$0}}'|排序|uniq-c|awk'{a=$1;$1="";print$0,a}'总结火焰图总结了一下,后面会讲到还有一种处理性能问题的方法。做开发的时间越长,越觉得工具的重要性,所以打算加一个专题来介绍一下自己使用的各种工具。当然,这也需要我对新的工具有更多的了解、使用和总结。近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.别在满屏的if/else中,试试策略模式,真的很好吃!!3.操!Java中xx≠null的新语法是什么?4、SpringBoot2.5发布,深色模式太炸了!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!
