3. 性能分析工具:Linux perf、火焰图、strace、top、vmstat、iostat 等工具的使用
说实话,性能优化这件事,工具用对了,问题就解决了一半。我见过太多人一上来就对着代码瞎猜,折腾半天找不到根因。其实 Linux 自带的那些工具,个个都是宝贝,关键是你得知道什么时候该用哪个。
这一章,我就带你把这些工具捋一遍。不是罗列参数,而是告诉你——我遇到瓶颈时,第一反应会摸哪个工具。
3.1 先看全局:top、vmstat、iostat
接到一个性能问题,我个人的习惯是:先看全局,再定焦点。别一上来就 strace 挂上去,那是最后一步。
3.1.1 top —— 系统负载的第一眼
top 是我每天敲得最多的命令。没什么花哨,但信息密度极高。
$ top
top - 14:23:11 up 3 days, 2:15, 3 users, load average: 2.15, 1.89, 1.45
Tasks: 245 total, 1 running, 244 sleeping, 0 stopped, 0 zombie
%Cpu(s): 45.2 us, 12.8 sy, 0.0 ni, 41.5 id, 0.2 wa, 0.0 hi, 0.3 si
MiB Mem : 15984.3 total, 2345.6 free, 8912.3 used, 4726.4 buff/cache
MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 5678.9 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4567 root 20 0 12.5g 2.3g 45000 S 120.3 14.7 345:23.45 java
1234 mysql 20 0 1.8g 890m 12000 S 45.2 5.6 120:12.33 mysqld
我一般先看三行:
- load average:三个数分别代表 1/5/15 分钟平均负载。如果 1 分钟远高于 15 分钟,说明问题正在发生。
- %Cpu(s):us 是用户态,sy 是内核态,wa 是 IO 等待。wa 高了,别折腾 CPU,去看磁盘。
- %MEM 和 RES:RES 是物理内存占用,VIRT 是虚拟内存,别被 VIRT 吓到。
P 键按 CPU 排序,按 M 键按内存排序。排查问题时,我习惯先按 CPU 排,看看谁在吃资源。
3.1.2 vmstat —— 系统瓶颈的快速扫描仪
top 看的是快照,vmstat 看的是趋势。我通常这样用:
$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 2345600 45000 4678000 0 0 12 34 1200 4500 45 13 41 1 0
3 1 0 2341000 45000 4679000 0 0 890 56 1500 5200 48 15 35 2 0
重点看这几列:
- r:运行队列。如果持续大于 CPU 核数,说明 CPU 不够用。
- b:阻塞队列。b 大于 0 且持续增长,大概率是磁盘 IO 在拖后腿。
- si/so:swap 换入换出。这两个数不为 0,说明内存不够了,系统在疯狂换页。
- bi/bo:磁盘读写块数。bi 高说明读磁盘多,bo 高说明写磁盘多。
3.1.3 iostat —— 磁盘到底行不行
vmstat 告诉你磁盘有压力,iostat 告诉你压力在哪块盘、有多严重。
$ iostat -x 1 3
Device r/s w/s rkB/s wkB/s await svctm %util
sda 45.2 120.3 1800.5 4800.2 12.3 2.1 35.2
sdb 890.0 12.0 35600.0 480.0 120.5 8.9 99.8
我重点关注:
- %util:接近 100% 说明磁盘已经满负荷了。但注意,SSD 和 HDD 的 %util 含义不同,SSD 可以承受更高的并发。
- await:IO 请求的平均等待时间。超过 100ms 基本可以断定磁盘是瓶颈。
- svctm:IO 服务时间。如果 svctm 远小于 await,说明请求在排队,磁盘忙不过来了。
3.2 深入进程:perf 与火焰图
全局看完了,锁定了一个进程(比如那个吃 CPU 的 Java 进程),接下来就要钻进去看它到底在干什么。
3.2.1 perf —— 采样分析的王牌
perf 是 Linux 内核自带的性能剖析工具。它的原理很简单:以固定频率采样 CPU 正在执行的指令,然后统计每个函数出现的次数。
# 采样 10 秒,记录 CPU 时钟事件
$ perf record -F 99 -p 4567 -- sleep 10
# 生成报告
$ perf report
输出会告诉你,CPU 时间花在了哪些函数上。比如:
Samples: 9K of event 'cpu-clock', Event count (approx.): 9000000
Overhead Command Shared Object Symbol
45.2% java libjvm.so [.] G1ParScanThreadState::trim_queue
12.8% java libjvm.so [.] InstanceKlass::oop_size
8.5% java libzip.so [.] inflate
看到这个结果,我就能判断:45% 的 CPU 花在了 GC 的扫描队列上,说明 GC 压力大。8.5% 花在解压缩上,可能是日志或数据传输的瓶颈。
-F 99:每秒采样 99 次,避免采样频率与程序周期同步产生偏差。-p PID:只采样指定进程。-g:记录调用栈,生成火焰图必备。
3.2.2 火焰图 —— 一眼看穿热点路径
perf report 的输出是文本,不够直观。火焰图把调用栈可视化,一眼就能看出哪条路径最热。
生成火焰图的步骤:
- 用 perf 采集带调用栈的数据:
perf record -F 99 -p PID -g -- sleep 30 - 生成折叠栈文件:
perf script > out.perf - 用 FlameGraph 工具生成 SVG:
stackcollapse-perf.pl out.perf > out.folded,然后flamegraph.pl out.folded > flame.svg
- 每个矩形代表一个函数调用,宽度代表它在采样中出现的比例。
- 从下往上看,最底层是入口函数,顶层是叶子函数。
- 找最宽的「平顶山」——那就是最耗 CPU 的地方。
- 如果顶层函数是系统调用(如
sys_read、sys_write),说明程序在频繁做 IO。
下面是我用 SVG 画的一张火焰图结构示意,帮你理解它的布局逻辑:
3.3 追踪系统调用:strace
perf 看 CPU 热点,strace 看系统调用。当程序表现异常——比如卡住、慢、报奇怪的错误——strace 就是我的第一把手术刀。
# 追踪指定进程的系统调用
$ strace -p 4567 -c
# 只看 write 和 read 调用
$ strace -p 4567 -e trace=write,read
# 统计系统调用次数和耗时
$ strace -p 4567 -c -S time
输出示例:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
45.23 2.345678 12.34 190000 write
32.15 1.678901 8.90 188000 read
12.34 0.654321 5.67 115000 poll
5.67 0.298765 3.21 93000 epoll_wait
-p 长时间追踪。我一般先用 -c 做统计模式,确认有问题再用 -e trace= 缩小范围。
我遇到过最典型的场景:一个服务每隔几秒就卡顿一次。用 strace 一看,发现每隔 5 秒就有一个 write 调用耗时 800ms。顺藤摸瓜找到日志框架,发现它在每次写日志时都会刷新磁盘。改成异步刷盘后,卡顿消失。
3.4 工具选择矩阵
这么多工具,到底先用哪个?我整理了一个选择矩阵,帮你快速定位:
| 现象 | 首选工具 | 看什么指标 | 下一步 |
|---|---|---|---|
| CPU 高 | top → perf | us/sy 比例、热点函数 | 火焰图定位代码 |
| 内存高 | top → vmstat | RES、si/so | 检查 GC 或内存泄漏 |
| IO 慢 | iostat → vmstat | %util、await、b 列 | strace 追踪读写调用 |
| 响应卡顿 | strace -c | 系统调用耗时分布 | 缩小到具体调用 |
| 负载高但 CPU 空闲 | vmstat → iostat | b 列、bi/bo | 检查磁盘或锁竞争 |
3.5 实战案例:一次完整的排查流程
光讲理论没意思,我拿一个真实案例串一遍:
现象:线上 API 服务,平时响应 20ms,最近涨到 200ms。
第一步:top 看全局
发现 CPU 空闲 70%,但 load average 高达 8。wa 列 25%。嗯,问题不在 CPU,在 IO。
第二步:iostat 看磁盘
iostat -x 1 一看,sdb 的 %util 99.8%,await 150ms。磁盘打满了。
第三步:iotop 找进程
用 iotop 看到是 Java 进程在疯狂写磁盘。每秒写 200MB。
第四步:strace 看写什么
strace -p PID -e trace=write -c 发现 90% 的 write 调用来自日志框架。再细看,日志文件在 sdb 上。
第五步:解决问题
把日志目录移到 sda(SSD),调整日志刷盘策略为每秒批量刷一次。响应时间降回 25ms。
3.6 本章小结
工具不在多,在于会用。我个人的工具箱里,最常用的就是这六个:
- top:第一眼,看系统整体健康度。
- vmstat:第二眼,看 CPU/内存/IO 的平衡。
- iostat:第三眼,看磁盘到底行不行。
- perf + 火焰图:钻进去,看 CPU 花在哪。
- strace:最后一步,看系统调用细节。
记住一个原则:从外到内,从粗到细。别一上来就 strace,也别只看 top 就下结论。工具链是串起来的,不是单点作战。