跳转到内容

线上 CPU 飙升 100%?一整套标准排查 SOP

线上 CPU 100% 的定位方法是固定的两步:先找到吃 CPU 的线程,再把线程 ID 和 JVM 堆栈对上。本文给出两套工具链——不依赖任何安装的 top/jstack 命令流(适用于限制严格的内网环境),以及 Arthas(一条命令直接定位到代码行)。

一个前提:不要上来就重启。重启会销毁现场,而且流量还在,另一台机器很快会重现同样的问题。

阶段一:命令流(不装任何工具)

Section titled “阶段一:命令流(不装任何工具)”

有些内网环境不允许安装外部诊断工具,必须会用系统自带的工具。

登录服务器,输入 top,按 P(按 CPU 排序),找到占用最高的 PID(假设是 10245)。

Step 2: 找出进程里的问题线程 (top -Hp)

Section titled “Step 2: 找出进程里的问题线程 (top -Hp)”

一个 Java 进程里可能有几百个线程,要定位到具体哪一条。 运行:

Terminal window
top -Hp 10245

这会列出该进程下的所有线程。假设发现线程 ID 10250 一直占着 90% 的 CPU。

把线程 ID(10250)转成十六进制——JVM 栈快照里的线程 ID 是十六进制显示的。

Terminal window
printf "%x\n" 10250
# 输出: 280a

Step 4: 打印线程快照并定位代码 (jstack)

Section titled “Step 4: 打印线程快照并定位代码 (jstack)”

导出该进程的所有线程堆栈,用 grep 定位刚才的线程:

Terminal window
jstack 10245 | grep -A 20 "280a"

输出中就能看到具体代码位置,例如: at com.company.utils.RegexUtil.match(RegexUtil.java:45)

阶段二:现代诊断工具(使用 Arthas)

Section titled “阶段二:现代诊断工具(使用 Arthas)”

在允许安装诊断工具的环境中,推荐直接使用阿里开源的 Java 诊断工具 Arthas(官方文档),它能大幅缩短上面的定位链路。

如果 CPU 飙高,直接运行 Arthas 并进入控制台。只需敲一个命令:

Terminal window
dashboard

或者直接找出当前 CPU 占用前 3 名的线程,并打印出它们的堆栈:

Terminal window
thread -n 3

屏幕上直接就会出现引发飙高的具体代码行数,省略了上面传统做法的繁琐步骤。

即便你找到了是哪行代码在跑,你还需要知道为什么。通常导致 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 分析