火焰图分析Java程序瓶颈
发布于2025-03-16 01:13:24,更新于2025-03-23 19:08:11,标签:java devops 文章会持续修订,转载请注明来源地址:https://meethigher.top/blog最近手撕的 tcp-reverse-proxy,相比 nginx 来说,高并发时整体吞吐量太差了,需要定位具体的性能损耗点。因此特意了解了下火焰图。
一、火焰图
1.1 概念
火焰图(Flame Graph) 是一种用于可视化性能分析数据的图形化工具,它通过直观的方式展示程序在运行时的资源使用情况(如 CPU 时间、内存分配、I/O 时间等),帮助开发者快速定位性能瓶颈。
1.2 基本构成
火焰图由一个二维坐标系构成
- x 轴
- 含义:表示某种资源的占比,如 CPU 时间、内存大小、IO 时间等。
- 说明:宽度越大、表示该函数消耗的资源越多。
- y 轴
- 含义:表示调用栈的深度。
- 说明:顶层是实际执行函数,下层是调用顶层的函数。
二、async-profiler
针对 Java 程序,如果想要生成火焰图,通过 JDK 自带的 jstack 与 fastthread 即可生成。但是这个过程太过麻烦。
在实际中,更推荐使用轻量工具 async-profiler,这个工具主要适用于 Linux 环境。
2.1 安装
配置 bash 脚本,直接运行
1 |
|
运行 asprof -v
2.2 解读火焰图
该内容摘自 async-profiler/docs/FlamegraphInterpretation.md at master · async-profiler/async-profiler
1.) 首先是示例代码
1 | main() { |
2.) asprof 进行采样。每秒采样一次样本,每次采样时,都会保存其当前调用堆栈,如下图。
3.) 对采样数据进行分类。由上图采样结果可得。
- func3()->func7():3 样本
- func4(): 1 样本
- func1()->func5():2 样本
- func2()->func6()->func8():4 样本
- func2()->func6(): 1 样本
4.) 排序,由按照字母排序,排序优先级由底层到上层。
5.) 聚合。将深度相同的函数拼成一块,获得聚合视图。
6.) 解读。在此示例中
- func4、func5、func6、func7、func8 都是实际消耗资源的函数。
- 在实际开发中,像 func4 这类是比较接近底层的函数。这种底层的优化,我们可以暂时忽略。主要关注顶层优化,也就是像 func8 这种。
另外 asprof 生成的火焰图,也有颜色说明。如下
2.3 生成火焰图
以下火焰图的生成,是基于我手撕的 tcp-reverse-proxy。
运行 java 程序后,执行命令 ps -ef|grep java
可以获取 PID。
详细的分析模式,可以自行查阅官方文档 Profiling modes。
下面只记录常用的分析模式。
2.3.1 CPU
运行命令,采集 30 秒目标进程 (PID 为 111) 的 CPU 占用情况
1 | asprof -d 30 -f cpu.html 111 |
搜索我的包名 top.meethigehr
,通过火焰图,呈现为紫色,可以定位到以下两个函数还是可以进一步优化的。
分别是如下这两个函数
1 | /** |
2.3.2 内存
运行命令,采集 30 秒目标进程 (PID 为 111) 的内存分配情况
1 | asprof -d 30 -e alloc -f mem.html 111 |
搜索我的包名 top.meethigher
,通过火焰图,呈现为紫色,矛头还是直指 top.meethigher.proxy.http.ReverseHttpProxy#getProxyUrl
,他居然平分了我一个函数的一半内存,比 vertx 内存占用还要高。
2.3.3 锁消耗 / 锁等待
运行命令,采集 30 秒目标进程 (PID 为 111) 的锁占用的状态
1 | asprof -d 30 -e lock -f lock.html 111 |
2.3.4 I/O 时间
运行命令,采集 30 秒目标进程 (PID 为 111) 的运行延时状况
1 | asprof -d 30 -e wall -f lock.html 111 |
可以看到更多的时间,还是花在 write 上了。
2.4 与 Jar 集成
上面介绍的生成火焰图的方式,适用于长时间运行的程序。
如果一个本身运行时间就很短的程序,用上述方式就不行了。就比如,我在使用 vertx 的 httpclient 时,发现他对于域名的解析,比 okhttp 要慢很多。我需要针对 httpclient 的实现,进行火焰图的生成,以定位问题所在。
这类情况,可以将生成火焰图与 Jar 进行集成,让 asprof 伴随 jar 的整个生命周期。
1 | java -agentpath:/usr/local/async-profiler/lib/libasyncProfiler.so=start,event=cpu,file=cpu-okhttp.html -jar bug-test-okhttp.jar |
三、JetBrains IDEA Profier
async-profiler 是在 Linux 环境中使用的,但是有些问题,只有在 Windows 系统上可以复现,这就需要在 Windows 上也能生成火焰图。
在 Windows 上直接使用 IDEA 内置工具就可以,比如我的 IDEA 版本是 IntelliJ IDEA 2024.1.2 (Ultimate Edition)
,运行程序时,选择 Profiler 'main()' with 'Intellij Profiler'
即可
四、参考
async-profiler/docs/FlamegraphInterpretation.md at master · async-profiler/async-profiler