火焰图(Flame Graph)作为一款强大的可视化性能分析工具,能帮助我们快速定位CPU、内存或I/O方面的瓶颈问题,让性能优化工作变得更加高效。今天就来给大家详细讲讲火焰图的使用方法。

一、火焰图能解决哪些问题?

  1. CPU占用高:当我们遇到CPU占用过高的情况时,火焰图可以清晰地展示出哪些函数消耗了最多的CPU时间,让我们能快速锁定问题函数。
  2. 程序卡顿:它能深入分析代码的执行路径,帮我们找到那些导致程序运行缓慢的慢速操作,进而优化程序性能。
  3. 线程阻塞:火焰图还能查看哪些线程在等待锁或者I/O资源,方便我们排查线程阻塞问题,提高程序的并发性能。
  4. 内存泄漏(需配合内存分析工具):虽然火焰图不能单独检测内存泄漏,但配合其他内存分析工具,它可以在内存性能分析方面发挥重要作用。

二、使用火焰图前的准备工作(Linux / macOS系统)

(一)安装perf(Linux性能分析工具)

在不同的Linux发行版中,安装perf的命令有所不同:

  • Ubuntu/Debian系统:在终端输入sudo apt install linux-tools-common linux-tools-generic。这条命令会安装perf工具及其相关依赖,sudo表示以管理员权限运行,apt是Ubuntu/Debian系统的包管理工具,install用于安装软件包。
  • CentOS/RHEL系统:使用sudo yum install perf命令进行安装。yum是CentOS/RHEL系统的包管理工具,同样通过sudo获取管理员权限来安装perf。

(二)安装FlameGraph脚本

FlameGraph脚本是生成火焰图的关键工具,我们可以通过Git来获取它:

  1. 在终端执行git clone https://github.com/brendangregg/FlameGraph.git。这条命令会从指定的GitHub仓库克隆FlameGraph项目到本地。
  2. 进入克隆下来的FlameGraph目录:cd FlameGraph
  3. 将FlameGraph目录临时添加到系统的PATH环境变量中,执行export PATH=$PATH:$(pwd) 。这样,系统就能在当前目录下找到相关的脚本文件,方便后续生成火焰图。

三、生成CPU火焰图(以Java为例)

(一)采集数据

  1. 方法1:使用perf(推荐,适用于Linux)
    • 找到Java进程ID:可以使用jps命令,如果系统没有安装jps,也可以用ps -ef | grep java命令来查找Java进程的ID。这两个命令的作用都是在系统进程列表中找到正在运行的Java进程,并显示其进程ID。
    • 采集CPU调用栈(采样30秒):在终端输入sudo perf record -F 99 -p -g -- sleep 30 。这里sudo获取管理员权限,perf record用于记录性能数据,-F 99表示每秒采样99次,-p后面需要跟上前面查到的Java进程ID,-g表示记录调用栈信息,-- sleep 30表示采样持续30秒。
    • 生成火焰图:执行perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg。这条命令通过管道操作,将perf script采集到的数据依次经过stackcollapse-perf.plflamegraph.pl脚本处理,最终生成名为flamegraph.svg的火焰图文件。
  2. 方法2:使用async-profiler(更简单,支持Java)
    • 下载async-profiler:在终端执行wget https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-x64.tar.gz ,这条命令会从指定的GitHub发布页面下载async-profiler的压缩包。然后解压压缩包,执行tar -xzf async-profiler-.tar.gz ,并进入解压后的目录:cd async-profiler-
    • 采集CPU数据(采样30秒):在解压后的目录下执行./profiler.sh -d 30 -f flamegraph.svg-d 30表示采样时间为30秒,-f flamegraph.svg指定生成的火焰图文件名为flamegraph.svg

(二)查看火焰图

生成的flamegraph.svg文件可以用浏览器打开,比如使用firefox flamegraph.svg,当然也可以用Chrome、Edge等其他浏览器打开,这样就能直观地查看火焰图了。

四、如何读懂火焰图?

(一)火焰图结构

  1. Y轴(高度):代表调用栈深度,越往火焰图的下方,调用链就越长。就好比一个函数调用了其他函数,其他函数又继续调用别的函数,调用链不断延伸,在火焰图上就体现为Y轴方向的深度增加。
  2. X轴(宽度):表示函数执行时间占比,函数在X轴上显示得越宽,说明它占用的CPU时间越多,也就意味着这个函数可能是导致性能问题的关键所在。
  3. 颜色:火焰图中的颜色并没有特殊含义,只是为了区分不同的函数,让我们在查看火焰图时能更清晰地分辨各个函数。

(二)关键操作

  1. 鼠标悬停:当我们把鼠标悬停在火焰图的某个函数区域上时,会显示出该函数的名称以及它占用CPU的比例,方便我们快速了解函数的基本信息。
  2. 点击放大:点击火焰图中的某个函数区域,可以放大查看这个函数的详细调用链,深入分析函数内部的调用情况,找出可能存在的性能问题。
  3. 搜索(Ctrl+F):如果我们想查找特定的函数,比如java.lang.Thread.sleep,可以使用浏览器的搜索功能(一般是按下Ctrl+F组合键),在火焰图中快速定位到目标函数。

五、常见问题及解决方法

(一)火焰图显示[unknown]怎么办?

  1. 原因:出现这种情况通常是因为缺少调试符号,比如JVM没有启用-XX:+PreserveFramePointer参数。这个参数用于保留函数调用栈的指针信息,没有它,火焰图可能无法准确识别函数。
  2. 解决方案
    • Java运行时添加参数:在运行Java程序时添加-XX:+PreserveFramePointer参数,例如java -XX:+PreserveFramePointer -jar your_app.jar 。这样JVM在运行时就会保留相关指针信息,有助于火焰图准确识别函数。
    • 使用async-profiler的–all-user选项:使用async-profiler采集数据时,添加--all-user选项,命令为./profiler.sh --all-user -d 30 -f flamegraph.svg 。这个选项可以让async-profiler采集更多用户空间的信息,提高函数识别的准确性。

(二)如何分析内存泄漏?

如果要分析内存泄漏问题,可以改用内存火焰图(需借助async-profiler) 。在async-profiler的目录下执行./profiler.sh -d 30 -e alloc -f mem_flamegraph.svg-e alloc表示以内存分配事件为采样依据,-d 30还是采样30秒,-f mem_flamegraph.svg指定生成的内存火焰图文件名为mem_flamegraph.svg。通过分析这个内存火焰图,我们可以更直观地了解内存的分配情况,从而发现可能存在的内存泄漏问题。

六、进阶用法

  1. 分析锁竞争:在async-profiler目录下执行./profiler.sh -e lock -d 30 -f lock.svg-e lock表示以锁竞争事件为采样依据,采样30秒后生成名为lock.svg的火焰图,通过这个火焰图可以分析程序中的锁竞争情况,找出可能导致线程阻塞的锁。
  2. 分析I/O等待:在终端执行perf record -e 'sched:sched_stat_iowait' -p -g -- sleep 10 。这里-e 'sched:sched_stat_iowait'指定以I/O等待事件为采样依据,-p跟上目标进程ID,-g记录调用栈信息,-- sleep 10表示采样10秒。通过这种方式采集的数据生成的火焰图,可以帮助我们分析I/O等待问题。
  3. 生成差分火焰图:如果我们想对比两个不同状态下的火焰图差异,可以使用diff two_flamegraphs.svg命令。这里two_flamegraphs.svg需要替换为实际要对比的两个火焰图文件名,通过查看差分火焰图,我们能更清楚地看到两次采样之间的性能变化。

七、总结

  1. 安装工具:可以选择安装perf + FlameGraph组合,或者使用async-profiler工具,它们都能帮助我们生成火焰图。
  2. 采集数据:使用perf record命令或者async-profiler./profiler.sh -d 30命令来采集性能数据,采样时间可以根据实际情况调整。
  3. 生成SVG:利用flamegraph.pl脚本对采集到的数据进行转换,最终生成可供查看分析的火焰图文件(.svg格式)。

希望通过这篇文章,大家能对火焰图的使用有更深入的了解,在实际工作中借助火焰图更好地进行性能优化。