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_POINT

5. 大量线程上下文切换

# 现象: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 最高的线程 → 看它在跑什么代码 → 那行代码就是热点」

状态

  • 已背速记
  • 能讲通俗版
  • 能答追问