CPU 100% 排查命令序列
一句话速记
CPU 100% 排查五步走:① top → 确认是哪个 Java 进程;② top -H -p <pid> → 找到 CPU 最高的线程 ID;③ printf '%x\n' <tid> → 转十六进制;④ jstack <pid> | grep -A 30 <hex> → 看该线程在干什么;⑤ arthas thread -n 5 一步到位(替代 ②③④)。根因通常是:死循环、正则回溯、JSON 序列化大对象、GC 频繁。
通俗解释(5 分钟版)
CPU 100% 排查的本质:
1. 找到是哪个进程吃的 CPU → top
2. 找到进程里是哪个线程吃的 → top -H 或 arthas thread -n
3. 找到线程在做什么 → jstack / arthas thread <id>
4. 根据 stacktrace 判断根因
整个过程 30 秒:
top → arthas attach → thread -n 5 → 看到 hotspot 方法 → 定位代码行
关键细节
标准命令序列(不用 arthas)
# 步骤 1:找 CPU 最高的进程
top
# 看到 java 进程 CPU 100%,记下 PID=12345
# 步骤 2:找进程中 CPU 最高的线程
top -H -p 12345
# 看到线程 TID=12367 CPU 98%,记下 12367
# 步骤 3:TID 转十六进制
printf '%x\n' 12367
# 输出:304f
# 步骤 4:在线程 dump 中找这个线程
jstack 12345 | grep -A 30 '0x304f'
# 或存文件再查:
jstack 12345 > /tmp/jstack.log
grep -A 30 'nid=0x304f' /tmp/jstack.log
# 步骤 5:看 stacktrace 分析根因用 arthas(推荐,一步到位)
# 安装 + 启动
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 选择 CPU 高的那个进程
# 一步到位:找 CPU 最高的 5 个线程
thread -n 5
# 输出示例:
# "http-nio-8080-exec-3" Id=42 cpuUsage=98% RUNNABLE
# at com.example.service.ReportService.generateReport(ReportService.java:156)
# at com.example.controller.ReportController.export(ReportController.java:32)
# ...
# 直接看到:ReportService.generateReport() 第 156 行在吃 CPU常见根因及 stacktrace 特征
1. 死循环
stacktrace 特征:同一个方法反复出现在顶部,没有 IO/阻塞操作
"http-nio-8080-exec-3" RUNNABLE
at com.example.util.LoopUtil.process(LoopUtil.java:25)
at com.example.util.LoopUtil.process(LoopUtil.java:28) ← 递归/循环
at com.example.util.LoopUtil.process(LoopUtil.java:28) ← 无限循环
常见代码:
while (true) { ... } 或 递归没终止条件
HashMap 在并发 rehash 时死循环(JDK 7)
2. 正则回溯(Catastrophic Backtracking)
stacktrace 特征:在 java.util.regex.Pattern 的方法中
"http-nio-8080-exec-5" RUNNABLE
at java.util.regex.Pattern$Curly.match(Pattern.java:4234)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4568)
at java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:3802)
...
常见代码:
String.matches("(a+)+b") 输入 "aaaaaaaaaaaaaaaaaaaac"
→ 指数级回溯,100 个字符可能跑几分钟
3. JSON 序列化大对象
stacktrace 特征:在 Jackson/Gson/Fastjson 的序列化方法中
"http-nio-8080-exec-7" RUNNABLE
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(...)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(...)
...
常见场景:
返回一个没做分页的 List(几万条记录)
Bean 里循环引用导致序列化死循环(@JsonIgnore 忘了加)
Fastjson 的 toString() 触发序列化整个懒加载对象
4. GC 频繁(表现是 CPU 高)
# 现象:CPU 高但不是业务线程,而是 GC 线程
top -H -p <pid>
# 看到 "GC task thread#0 (ParallelGC)" CPU 很高
# 验证:
jstat -gc <pid> 1s
# YGC/FGC 每秒都有几十次 → GC 占满 CPU
# 这和死循环 CPU 高的区别:
# 死循环:业务线程 CPU 高
# GC 频繁:GC 线程 CPU 高 + 业务线程经常在 SAFE_POINT5. 大量线程上下文切换
# 现象:CPU 高但 sys%(system CPU)占比高,而非 usr%(user CPU)
vmstat 1
# sy 列 > 30% → 大量系统调用/上下文切换
# 常见原因:
# 线程数过多(> 1000)→ 上下文切换吃掉 CPU
# 锁竞争太激烈(synchronized 导致频繁的锁争抢)排查速查表
你看到的 是什么问题 用什么查
───────────────────────────────────────────────────────────────
业务线程 CPU 高 + 同一方法反复出现 死循环/计算密集 arthas thread -n
Pattern 类方法在栈顶 正则回溯 检查正则表达式
Jackson/Gson 方法在栈顶 序列化大对象 检查返回数据大小
GC 线程 CPU 高 GC 频繁 jstat -gc + GC 日志
sys% > 30% 线程/锁问题 jstack + vmstat
某个线程 CPU 忽高忽低(周期性) 定时任务/心跳 看线程名和 stacktrace
延伸追问
- Q:CPU 100% 时连 arthas 都连不上怎么办?
→ CPU 被完全占满时,jstack/jstat 可能超时。可以:①
kill -3 <pid>信号触发线程 dump 输出到 stdout/日志(不依赖 JVM 响应);② 用top -H -b -n 1至少看一眼哪个线程;③ 如果 k8s 环境,给 pod 临时加 CPU limit。 - Q:多核 CPU,top 显示 100% 但可能是单核 100% 还是全核?
→ top 默认显示的是百分比是单核的。4 核机器上 top 显示 100% = 1 个核打满(25% 总 CPU)。按
1键展开看每核使用率。真正的”全核 100%“是 400%(4 核都满)。 - Q:arthas
thread -n 5看到的是瞬时 CPU 还是累积? → arthas 默认采样间隔 100ms(可配-i),看的是瞬时 CPU 使用率。适合找”当前正在吃 CPU 的线程”。如果是周期性 burst,可能需要多执行几次。
我的记法
- 五步法:top → top -H → printf %x → jstack grep → 看 stacktrace
- arthas 一步:
thread -n 5替代后面四步 - 五大根因:死循环、正则回溯、序列化大对象、GC 频繁、线程上下文切换
- 一句话:「找到 CPU 最高的线程 → 看它在跑什么代码 → 那行代码就是热点」
状态
- 已背速记
- 能讲通俗版
- 能答追问