先分清是哪种 CPU 高
告警说"CPU 100%",先看是用户 CPU还是系统 CPU(top 里 us 和 sy 列)。us 高多半是业务线程在烧,sy 飙高往往跟系统调用、锁、上下文切换有关。top 整体高但 Java 进程不高,问题在别的进程;top 看 Java 进程高,再进 top -Hp PID 看具体哪个线程。
四步定位法
# 1. 找占 CPU 最高的 Java 线程
$ top -Hp $PID
# PID USER %CPU ...
# 7890 app 96.3 ... ← 这位
# 2. 线程 ID 转十六进制
$ printf "%x
" 7890
# 1ed2
# 3. jstack 抓栈,grep 这个 nid
$ jstack $PID | grep -A 30 "nid=0x1ed2"
# "pool-3-thread-7" prio=5 tid=0x... nid=0x1ed2 runnable
# at java.util.regex.Pattern$Curly.match0(Pattern.java:4218)
# at java.util.regex.Pattern$GroupHead.match(Pattern.java:4660)
# ... 看:CPU 烧在正则匹配上
# 4. 怀疑 GC 在烧 CPU?看 GC 日志 + jstat
$ jstat -gcutil $PID 1000 3
# YGC YGCT FGC FGCT —— FGCT/YGCT 暴涨说明 GC 在生消耗四种根因与对策
死循环/无出口递归:栈帧一直压在同一个方法,jstack 多次采样栈不变即锁定。对策是改代码。曾见过 while(flag) 里的 flag 跨线程可见性没保证,一直看不到 flag 被设——加 volatile 解决。GC 抢核:jstack 线程看着像"GC thread",jstat 看 YGCT/FGCT 在涨。根因是内存压力——频繁回收或回收效率低,得回头查内存问题不是调 GC 参数能解决的。锁自旋/活锁:线程状态是 RUNNABLE 但实际在 synchronized 或 Lock 上空转——jstack 里大量线程卡在同一 monitor。曾经一次线上 CPU 高,结果是 AQS 队列上的线程在 spin。对策是改并发策略。正则灾难:上面案例那种,恶意的正则(如 (a+)+)输入特定字符串会指数级回溯,一个匹配烧干一个核。对策是预编译 + 长度限制 + 避免灾难性正则。
火焰图:把"谁烧得多"画出来
多次采样 jstack 拼接成火焰图,方法占的横向越宽说明烧 CPU 越多。Linux 下用 perf,Java 用 async-profiler 更合适(它走 JVM 的 AsyncGetCallTrace,不靠 jstack 那种"快照"采样):./profiler.sh -d 30 -f flame.html PID 采样 30 秒,生成可交互的 SVG 火焰图,比一堆栈帧直观十倍。
预防:把排查前置成监控
CPU 飙高永远比内存问题紧急——告警响了就在事故里了。把自动采样脚本前置:CPU 超过阈值 80% 持续 1 分钟,自动跑一次 top -H + jstack + jstat 把现场存档——这样告警一来人还没上线,现场已经拍好了。这种"现场自动取证"思路同样适用于内存——下一篇讲内存泄漏排查完整版。
评论 (0)