跳到主要内容

CPU 飙高排查

问题

生产环境 CPU 直接飙到 100%,如何快速定位和解决?

答案

排查四步法

第一步:找到高 CPU 进程

top 查看进程 CPU 排名
top -c
# 按 P 键按 CPU 排序
# 找到占用 CPU 最高的 Java 进程 PID,假设是 12345

第二步:找到高 CPU 线程

查看进程内线程 CPU 排名
top -Hp 12345
# 找到 CPU 最高的线程 TID,假设是 12378

# 将线程 TID 转为十六进制(jstack 用十六进制)
printf "%x\n" 12378
# 输出 305a

第三步:导出线程堆栈

jstack 导出线程栈
jstack 12345 > thread_dump.txt

# 搜索十六进制线程 ID
grep -A 30 "nid=0x305a" thread_dump.txt

第四步:分析堆栈

线程堆栈示例
"pool-1-thread-3" #15 prio=5 os_prio=0 tid=0x00007f nid=0x305a runnable
java.lang.Thread.State: RUNNABLE
at com.example.service.OrderService.calculateDiscount(OrderService.java:156)
at com.example.service.OrderService.processOrder(OrderService.java:89)
...

线程状态 RUNNABLE + 一直在执行某个方法 → 定位到具体的代码行。

常见 CPU 飙高原因

原因表现解决
死循环RUNNABLE,堆栈固定在某一行修复循环退出条件
正则回溯RUNNABLE,在 Pattern.matcher优化正则或加超时
频繁 Full GC多个 GC 线程 RUNNABLE排查内存问题
大量线程竞争BLOCKED 线程多减小锁粒度
序列化/反序列化CPU 密集型计算换高性能方案
加密/压缩CPU 密集型计算异步化或硬件加速

使用 Arthas 一键排查

Arthas thread 命令
# 启动 Arthas
java -jar arthas-boot.jar

# 查看最忙的 N 个线程
thread -n 3

# 查看线程占用 CPU 排名
thread --all

# 查看死锁
thread -b
Arthas profiler(火焰图)
# 生成 CPU 火焰图,直观看到热点方法
profiler start
# 等待 30 秒
profiler stop --format html --file /tmp/cpu-profile.html
小技巧
  • 多次 jstack 对比:同一个线程如果每次都卡在同一行 → 死循环
  • jstack 建议连续导出 3-5 次,每次间隔几秒

正则回溯导致 CPU 100% 案例

有问题的正则
// 贪婪匹配 + 回溯 → 灾难性回溯
String regex = "(a+)+b";
String input = "aaaaaaaaaaaaaaaaaaaaaaaac"; // 不匹配但会疯狂回溯
input.matches(regex); // CPU 100%
解决方案
// 1. 使用独占量词(禁止回溯)
String regex = "(a++)b";

// 2. 加执行超时
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Boolean> future = executor.submit(() -> input.matches(regex));
try {
future.get(1, TimeUnit.SECONDS); // 1 秒超时
} catch (TimeoutException e) {
future.cancel(true);
log.warn("正则匹配超时");
}

常见面试问题

Q1: CPU 100% 排查步骤是什么?

答案

四步法:top 找进程 → top -Hp 找线程 → printf "%x" 转十六进制 → jstack 匹配 nid 看堆栈。

Q2: 如何区分是业务代码还是 GC 导致的 CPU 高?

答案

  • GC 导致jstack 中大量 GC Thread 为 RUNNABLE;jstat -gcutil 显示 Full GC 频繁
  • 业务代码:用户线程 RUNNABLE,堆栈指向业务代码
  • 也可以看 top -Hp 中高 CPU 线程的名字,GC 线程名包含 "GC"

Q3: 如何预防 CPU 飙高?

答案

  • 正则表达式加超时保护
  • 循环中加退出条件 / 最大迭代次数
  • 合理的 JVM 内存配置(减少 Full GC)
  • 线程池合理配置(避免线程过多竞争)
  • 上线前压测,提前发现热点

相关链接