先分清是谁在烧 CPU
看到 CPU 打满,很多人的第一反应是“业务代码死循环了”,然后去翻最近上线的代码。这个方向只对了一半:CPU 高也可能是 GC 线程在疯狂回收、日志框架在同步刷盘、或者某个正则在大文本上回溯。方向错了,翻一天代码也找不到原因。
排查的第一步不是看代码,是看线程。进程级 CPU 只是一个总和,只有落到具体线程和栈上,才知道是谁在烧。
五分钟定位到热点线程
假设应用 PID 是 12345,先按线程看 CPU:
top -Hp 12345 -d 1
找到占用最高的线程 ID(十进制),转成十六进制,再去线程栈里搜:
printf '%x\n' 12377
jstack 12345 > /tmp/thread-1.txt
grep -A 30 'nid=0x3059' /tmp/thread-1.txt
jstack 输出里的 nid 是十六进制,和 top -H 的十进制对不上是新手最常见的卡点。也可以直接用 jcmd 12345 Thread.print,效果一样。
一个关键技巧:间隔 5 到 10 秒抓两到三次,看同一个方法是否稳定出现在栈顶。只抓一次,很可能抓到的是正在做 GC 或者刚巧在执行的短方法,误判率很高。
如果条件允许,用 async-profiler 直接出火焰图,比手工对线程高效得多:
./profiler.sh -d 30 -e cpu -f /tmp/cpu.html 12345
30 秒的采样能覆盖大部分抖动,时间太短会漏掉间歇性热点。async-profiler 3.x 把脚本改名为 asprof,参数基本一致,老项目里看到 profiler.sh 也不用奇怪。
如果热点线程是 GC
top -H 里排名靠前的线程名如果是 GC Thread#、G1 Young RemSet 这类,说明 CPU 花在垃圾回收上,业务线程其实在等。这时候再去看业务栈没有意义,要转向内存:
jstat -gcutil 12345 1000 10
看 Old 区使用率和 Full GC 次数。Full GC 后 Old 仍然居高不下,基本就是内存泄漏或堆太小;Young GC 极其频繁但每次回收量很大,可能是分配速率过高,比如循环里创建大对象、日志拼接、JSON 序列化。
还有一种容易被忽略的情况:容器里 top 看到的是宿主机视角,CPU 使用率可能包含其他容器。用 kubectl top pod 或 cgroup 的 cpu.stat 看容器自己的消耗,再结合 JVM 的 -XX:ActiveProcessorCount 确认线程数假设是否成立。
常见根因
死循环或无限重试。 典型是 while 等待某个条件,条件永远不满足;或者重试没有退避,失败后立刻再来一次。栈上会看到同一个方法反复出现。
正则回溯。 一个 (a+)+$ 之类的表达式,配上几 KB 的输入,就能让单核跑满几分钟。栈里通常是 Pattern、Matcher 相关方法,日志里能看到某条校验规则耗时异常。
日志或序列化开销。 在循环里 log.debug 拼字符串、对大对象做 toString、每次请求都全量序列化,CPU 会稳定地高,但火焰图很分散,没有单一热点。
锁竞争与自旋。 高并发下 CAS 失败重试、synchronized 争抢,CPU 上去了吞吐却没涨。线程栈里大量线程处于 BLOCKED 或反复执行 compareAndSet。
热点集合。 ConcurrentHashMap.computeIfAbsent 里放了耗时逻辑,或者用 AtomicLong 做全局计数,都会形成单点竞争。
常见坑
坑一:只看进程不看线程。 top 显示 800%,不知道是 8 个线程各 100% 还是一个线程打满多核,处理方式完全不同。
坑二:只抓一次线程栈。 抓到的是瞬时状态,可能刚好在 GC 或者在做类加载。多抓几次,看重复出现的栈。
坑三:容器里用错 PID。 jstack 要在容器内执行,PID 是容器内的 1 或者 Java 进程号,不是宿主机上的 PID。
坑四:线程 ID 没转十六进制。 十进制直接去 jstack 里搜,永远搜不到。
坑五:忽略 GC 线程。 看到 CPU 高就钻业务代码,结果根因是内存泄漏导致的频繁 Full GC。
坑六:采样时间太短。 10 秒的火焰图可能只覆盖了一次请求,间歇性热点完全看不到。
排查清单
- 确认影响范围:单实例还是全部实例,是否与发布、流量、定时任务时间点相关。
top -H找热点线程,转十六进制,jstack抓 2 到 3 次。- 热点是 GC 线程就转内存排查,是业务线程就看栈顶方法。
- 用 async-profiler 或 Arthas 的
thread -n 3交叉验证。 - 定位到代码后先做最小修复(降级、限流、关开关),再谈根治。
- 修复后用同样的压测流量验证 CPU 曲线和 P99 是否回到基线。
评论