本文目录导读:

当Java应用出现CPU飙高时,定位到具体代码行的标准排查流程如下:
快速定位步骤
1️⃣ 找到CPU最高的Java进程
# 方案1:top命令(推荐) top -c # 按大写P(CPU降序),找到进程PID # 方案2:ps命令 ps aux --sort=-%cpu | head -10
2️⃣ 找到进程内最耗CPU的线程
# 显示该进程的所有线程的CPU使用情况 top -H -p <PID> # 或使用ps命令 ps -mp <PID> -o THREAD,tid,time | sort -rnk 2 | head -20 # 记录下CPU占用最高的线程ID(TID,十进制)
3️⃣ 将线程ID转换为十六进制
# TID转换为十六进制 printf "%x\n" <TID> # printf "%x\n" 9876 输出 2694
4️⃣ 生成线程堆栈并定位
# 方案A:jstack(推荐,可多次采样) jstack <PID> | grep -A 30 <十六进制TID> # 方案B:打印所有线程堆栈 jstack <PID> > thread_dump.txt # 然后搜索十六进制TID # 方案C:实时跟踪 jstack -l <PID> | grep -E "(tid=0x[0-9a-f]+)" | grep -i "0x<十六进制TID>"
完整示例
# 1. 找到CPU高的进程 top -c # 假设看到 PID=12345 的Java进程CPU > 100% # 2. 查找该进程内CPU最高的线程 top -H -p 12345 # 假设 TID=23456 占CPU最高 # 3. 转16进制 printf "%x\n" 23456 # 输出: 5ba0 # 4. 查看线程堆栈 jstack 12345 | grep "0x5ba0" -A 30 # 会显示该线程的完整堆栈,定位到具体代码行
高级排查技巧
1 持续监控(采样多次)
# 每2秒采样一次,共5次
for i in {1..5}; do
jstack <PID> > dump_$i.txt
sleep 2
done
# 对比多次dump,看哪些线程始终处于RUNNABLE状态
2 使用arthas(阿里开源诊断工具)
# 安装arthas curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar <PID> # 在arthas控制台中 # 查看当前最忙的线程 thread -n 3 -i 2000 # 跟踪具体线程的堆栈 thread <threadId> # 监控方法调用耗时 monitor -c 5 com.example.service.UserService getUser
3 使用show-busy-java-threads脚本
# 一键定位最忙线程 wget --no-check-certificate https://raw.githubusercontent.com/oldratlee/useful-scripts/master/show-busy-java-threads chmod +x show-busy-java-threads ./show-busy-java-threads -p <PID>
常见CPU飙高场景与原因
| 现象 | 可能原因 | 定位线索 |
|---|---|---|
| 一直RUNNABLE | 死循环、大量计算 | 堆栈停在业务代码循环处 |
| 大量GC线程 | 频繁Full GC | jstat -gcutil |
| 线程阻塞 | 锁竞争激烈 | 堆栈显示BLOCKED或WAITING |
| 频繁上下文切换 | 大量短线程 | 查看线程数、线程dump中有大量新线程 |
应急处理方案
🔥 紧急降级方案
# 1. 生成堆栈快照 jstack <PID> > /tmp/stack_$(date +%Y%m%d_%H%M%S).txt # 2. 重启应用(仅限紧急情况) kill -9 <PID> # 先强杀 nohup java -jar app.jar > app.log 2>&1 & # 3. 限流降级(如果能操作) # 在网关层限制QPS,或者在应用代码中降级
排查实战清单
参考这个流程图:
CPU飙高
│
├── top -c → 找到Java进程PID
│
├── top -H -p <PID> → 找到最忙线程TID
│
├── printf "%x\n" <TID> → 转16进制
│
└── jstack <PID> | grep <0xTID> -A 30
│
├── 有业务代码 → 分析代码
│ 常见原因:
│ ├── 死循环
│ ├── 大对象/频繁GC
│ ├── 正则回溯
│ ├── 序列化/反序列化
│ └── 频繁的线程创建
│
└── 只有JVM内部栈帧 →
├── GC线程 → jstat -gcutil看GC
├── Compiler线程 → 可能是JIT编译
└── VM线程 → 可能是类加载/卸载
最关键的一点:一定要对比多次dump,只看一次dump可能会错过瞬态问题。