jstat、top、vmstat、jstack 在性能测试中的实际应用指南
一、工具定位与核心功能
| 工具 | 核心功能 |
|---|---|
| top | 全局监控 CPU、内存占用,定位高负载进程及线程(需转换线程ID为16进制)。 |
| vmstat | 分析 系统级资源瓶颈(CPU等待、内存交换、上下文切换、磁盘IO),识别硬件或OS层问题。 |
| jstat | 监控 JVM内存与GC行为,发现内存泄漏、GC频繁或内存分配不合理等问题。 |
| jstack | 生成 Java线程快照,诊断死锁、线程阻塞、CPU热点代码等并发问题。 |
二、四步联合诊断法
以下通过案例说明如何综合使用这四个工具:
场景描述
某电商系统在高并发压测时,响应时间从 50ms 骤增至 2s,请求失败率上升至 20%。
1. 第一步:使用 top 快速定位资源瓶颈
-
命令:
top -H -p <Java_PID> # 查看指定进程的线程级CPU和内存占用 -
输出解读:
- %CPU 列:发现某线程持续占用 90% CPU(例如线程ID=2567)。
- RES 列:检查内存是否异常增长。
-
关键操作:
- 记录高负载线程ID(如2567),转换为16进制(如
0xA07)。 - 输出示例:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2567 app 20 0 12.3g 2.1g 20m R 90.2 6.3 12:45.67 java
- 记录高负载线程ID(如2567),转换为16进制(如
2. 第二步:vmstat 分析系统级资源状态
-
命令:
vmstat 1 5 # 每秒采集一次,共5次 -
输出解读:
- procs:
r:运行队列长度(若持续>CPU核心数,说明CPU饱和)。b:阻塞进程数(高值可能意味着IO或锁竞争)。
- cpu:
us:用户态CPU(高值表示应用自身计算消耗)。sy:内核态CPU(高值表示系统调用频繁)。wa:IO等待时间(>20%可能存在磁盘或网络瓶颈)。
- memory:
si/so:交换内存页的进出(>0说明开始使用Swap,需警惕)。
- procs:
-
示例输出:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 12 0 0 1048576 4096 204800 0 0 0 5 120 4500 85 10 5 0 0 -
结论:
us=85%表明用户态CPU消耗高,可能是业务逻辑或GC导致;wa=0排除IO瓶颈。
3. 第三步:jstat 分析 JVM 内存与 GC
-
命令:
jstat -gcutil <Java_PID> 1000 5 # 每秒采集一次,共5次 -
输出示例:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 100.00 85.25 97.50 95.30 92.15 120 8.45 15 45.23 53.68 -
关键指标:
- O(Old区使用率):97.5% 接近满载,触发频繁 Full GC(FGC=15)。
- FGCT(Full GC总耗时):45.23秒,单次 Full GC 耗时约 3 秒,严重影响吞吐量。
-
结论:
内存泄漏或老年代容量不足导致频繁 Full GC,进一步加剧 CPU 消耗(top中高%CPU来自GC线程)。
4. 第四步:jstack 定位线程问题
-
生成线程快照:
jstack <Java_PID> > thread_dump.log # 输出到文件 -
分析步骤:
- 查找高CPU线程:
- 高CPU线程ID(0xA07)在日志中的
nid字段:"Thread-2567" #45 prio=5 os_prio=0 tid=0x00007fb1543b2000 nid=0xa07 runnable [0x00007fb1634f4000]
- 高CPU线程ID(0xA07)在日志中的
- 检查线程堆栈:
at com.example.OrderService.calculateDiscount(OrderService.java:45) at com.example.OrderController.createOrder(OrderController.java:32) - 识别热点代码:
calculateDiscount方法存在低效算法(如未缓存的复杂计算),或处于死循环。
- 查找高CPU线程:
-
补充排查:
- 死锁检测:
Found one Java-level deadlock: "Thread-1": waiting to lock monitor 0x00007fb1600001e8 (object 0x00000000fe73a1a8, a com.example.Resource), which is held by "Thread-2"
- 死锁检测:
三、典型问题与解决方案
案例 1:CPU 使用率飙升至 90%
- 现象:
top显示某Java线程持续高CPU,jstat中 YGC/FGC 正常。 - 分析:
jstack发现线程执行哈希计算(如密码加密),未使用缓存。vmstat显示us高、wa低,确认CPU密集型任务。
- 解决:
- 引入缓存(如 Redis)或优化算法复杂度。
案例 2:Full GC 频繁触发导致响应延迟
- 现象:
jstat显示O区使用率>95%,FGC每小时增加 50 次。 - 分析:
jmap -histo发现大量未关闭的数据库连接对象。jstack存在多个线程等待数据库连接池资源。
- 解决:
- 修复连接泄漏代码,调整连接池参数(
maxIdle,minEvictableIdleTimeMillis)。
- 修复连接泄漏代码,调整连接池参数(
案例 3:线程死锁导致请求卡顿
- 现象:
vmstat显示r列>10,jstat中GC正常,但top显示CPU使用率低。 - 分析:
jstack直接报告死锁信息,定位到两线程互相等待锁。
- 解决:
- 调整锁顺序,或使用无锁数据结构(如
ConcurrentHashMap)。
- 调整锁顺序,或使用无锁数据结构(如
四、脚本自动化采集示例
#!/bin/bash
PID=$1
DURATION=60 # 采集时长(秒)
INTERVAL=1 # 采集间隔
# 监控 top
top -b -H -d $INTERVAL -n $((DURATION/INTERVAL)) -p $PID > top.log &
# 监控 vmstat
vmstat $INTERVAL $((DURATION/INTERVAL)) > vmstat.log &
# 监控 jstat
jstat -gcutil $PID $INTERVAL $((DURATION/INTERVAL)) > jstat.log &
# 定时捕获 jstack
for i in $(seq 1 $((DURATION/5))); do
jstack $PID >> jstack.log
sleep 5
done
# 等待采集完成
wait
五、总结
- 组合策略:
top+vmstat定位系统层瓶颈(CPU/IO/内存)。jstat分析JVM内存模型与GC效率。jstack深入线程级代码实现。
- 核心原则:
- 从全局到局部:先确定系统瓶颈类型,再深入JVM和代码层。
- 多维度交叉验证:结合多个工具的数据排除误判(如高CPU可能是GC引起,而非业务代码)。
浙公网安备 33010602011771号