线上 CPU 飙升 100%?一整套标准排查 SOP
线上 CPU 100% 的定位方法是固定的两步:先找到吃 CPU 的线程,再把线程 ID 和 JVM 堆栈对上。本文给出两套工具链——不依赖任何安装的 top/jstack 命令流(适用于限制严格的内网环境),以及 Arthas(一条命令直接定位到代码行)。
一个前提:不要上来就重启。重启会销毁现场,而且流量还在,另一台机器很快会重现同样的问题。
阶段一:命令流(不装任何工具)
Section titled “阶段一:命令流(不装任何工具)”有些内网环境不允许安装外部诊断工具,必须会用系统自带的工具。
Step 1: 找出高 CPU 的进程 (top)
Section titled “Step 1: 找出高 CPU 的进程 (top)”登录服务器,输入 top,按 P(按 CPU 排序),找到占用最高的 PID(假设是 10245)。
Step 2: 找出进程里的问题线程 (top -Hp)
Section titled “Step 2: 找出进程里的问题线程 (top -Hp)”一个 Java 进程里可能有几百个线程,要定位到具体哪一条。 运行:
top -Hp 10245这会列出该进程下的所有线程。假设发现线程 ID 10250 一直占着 90% 的 CPU。
Step 3: 十六进制转换
Section titled “Step 3: 十六进制转换”把线程 ID(10250)转成十六进制——JVM 栈快照里的线程 ID 是十六进制显示的。
printf "%x\n" 10250# 输出: 280aStep 4: 打印线程快照并定位代码 (jstack)
Section titled “Step 4: 打印线程快照并定位代码 (jstack)”导出该进程的所有线程堆栈,用 grep 定位刚才的线程:
jstack 10245 | grep -A 20 "280a"输出中就能看到具体代码位置,例如:
at com.company.utils.RegexUtil.match(RegexUtil.java:45)
阶段二:现代诊断工具(使用 Arthas)
Section titled “阶段二:现代诊断工具(使用 Arthas)”在允许安装诊断工具的环境中,推荐直接使用阿里开源的 Java 诊断工具 Arthas(官方文档),它能大幅缩短上面的定位链路。
如果 CPU 飙高,直接运行 Arthas 并进入控制台。只需敲一个命令:
dashboard或者直接找出当前 CPU 占用前 3 名的线程,并打印出它们的堆栈:
thread -n 3屏幕上直接就会出现引发飙高的具体代码行数,省略了上面传统做法的繁琐步骤。
阶段三:常见 CPU 飙升元凶分析
Section titled “阶段三:常见 CPU 飙升元凶分析”即便你找到了是哪行代码在跑,你还需要知道为什么。通常导致 CPU 打满的元凶有以下三种:
1. 业务代码的死循环或低效算法
Section titled “1. 业务代码的死循环或低效算法”最典型的是 while(true) 缺乏终止条件,或者在巨大的数据集合里进行了 O(N^2) 级别的双层 for 循环嵌套。
特征:堆栈定位到业务的具体 Controller 或 Service 行数。
2. 正则表达式引发的回溯灾难 (Catastrophic Backtracking)
Section titled “2. 正则表达式引发的回溯灾难 (Catastrophic Backtracking)”如果你写了一个类似 ^(a+)+$ 这种嵌套量词的正则表达式,并且让它去匹配一段恶意构造的长文本,引擎会陷入指数级的尝试匹配中,瞬间锁死单核 CPU。
特征:堆栈卡在 java.util.regex.Pattern.match 等深层方法中。
解法:使用 RE2J 等无回溯风险的正则引擎,或优化表达式,禁止用户输入过长文本。
3. 垃圾回收器的死亡挣扎 (GC Thrashing)
Section titled “3. 垃圾回收器的死亡挣扎 (GC Thrashing)”这是最容易被误判的一种情况。
你通过 jstack 发现,占用 CPU 的并不是你的业务线程,而是叫 GC task thread#0 的线程。
这意味着你的程序其实没有死循环,而是发生 OOM(内存泄漏)的前兆! 内存快满了,JVM 试图进行 Full GC 来释放空间。但每次 STW 扫描了半天,只释放了 1% 的内存。下一秒又满了,又开始扫。CPU 全耗在垃圾回收上了。
解法:这不再是 CPU 的问题了,请立刻转入 OOM 内存排查流程(参考专栏下一篇《OOM 内存泄漏溯源实战》),去扒一扒到底是谁塞了几百万个对象在内存里不释放。
处理线上 CPU 报警的核心动作是:**先找线程,再看堆栈。**拿到带行号的快照后再判断属于哪一类元凶:
| 元凶 | 堆栈特征 | 处置 |
|---|---|---|
| 业务死循环 / 低效算法 | 卡在业务 Controller/Service 行 | 修代码,加终止条件或降时间复杂度 |
| 正则回溯灾难 | 卡在 java.util.regex 深层调用 | 换 RE2J 或重写表达式、限制输入长度 |
| GC Thrashing | 占用 CPU 的是 GC task thread | 转入内存排查流程,见 OOM 分析 |