线上服务器CPU飙升 Java代码 完整定位排查流程

约 2028 字

线上服务器CPU飙升 Java代码 完整定位排查流程(生产实战+面试标准答案)

线上CPU飙升是后端最高频故障,核心思路:服务器定位高CPU进程 → 定位高CPU线程 → 打印线程栈 → 锁定问题代码 → 分析根因。整套流程3分钟内可定位代码级问题。

一、整体排查链路(口诀)

top 找进程 → top -H 找线程 → printf 转16进制 → jstack 抓栈 → 定位代码行

二、第一步:服务器层找到高CPU Java进程

服务器执行:

top

观察:CPU占用最高的 PID(一般是java进程)。

假设高CPU进程PID = 12345

三、第二步:找到该进程下的【高CPU线程】

进入进程线程视图:

top -H -p 12345

-H:展示线程级别CPU占用

找到占用CPU最高的 线程TID,例如:67890

四、第三步:线程ID转16进制(jstack必备)

jstack 日志中线程ID是16进制,需要转换:

printf "%x\n" 67890

得到十六进制线程ID:10812

五、第四步:jstack 打印堆栈,精准定位代码行

jstack 12345 | grep -A 50 10812

-A 50:打印该线程后50行堆栈,直接看到类名、方法、代码行号

此时可以直接锁定:哪一行代码死循环、频繁计算、卡死

六、补充:全程一键排查命令(生产神器)

线上可直接复制执行,自动抓取最高CPU线程堆栈:

pid=$(top -bn1 | grep java | awk '{print $1}' | head -1)
tid=$(top -Hbn1 -p $pid | awk '{print $1}' | head -2 | tail -1)
hex_tid=$(printf "%x\n" $tid)
jstack $pid | grep -A 100 $hex_tid

七、CPU飙升常见Java代码根因(99%场景)

1. 死循环 / 空循环(最常见)

while(true)、for循环无退出条件、递归死递归

现象:单个线程CPU 100%,线程栈固定卡在某行代码

2. 复杂逻辑疯狂计算、大数据遍历

超大List/Map遍历、嵌套循环、笛卡尔积、复杂排序

数据量小时没问题,线上数据量大直接CPU打满

3. 正则表达式回溯卡死

贪婪正则、复杂匹配导致 正则回溯爆炸,CPU飙升

4. 频繁FullGC / GC线程占满CPU

不一定是业务代码!GC线程CPU高也是CPU飙高主力场景

排查命令:

jstat -gc 12345 1000

频繁 FGCT 上涨 → 内存溢出、内存泄漏、大对象创建

此时需要配合 jmap dump 分析:

jmap -dump:format=b,file=heap.hprof 12345

5. 锁自旋、CAS空转

大量竞争锁、Unsafe CAS空转、synchronized 严重竞争导致线程自旋消耗CPU

6. 定时任务频繁执行、死锁循环任务

quartz、xxl-job 任务逻辑死循环、每秒高频执行

八、如何区分:业务线程CPU高 VS GC线程CPU高

  • 业务线程高:jstack 能看到业务代码堆栈 → 业务逻辑问题
  • GC线程高:jstack 看不到业务栈,GC任务爆满 → 内存问题(泄漏/大对象/频繁创建销毁)

九、临时止血方案(线上紧急处理)

  1. 临时重启服务(快速恢复可用性)
  2. 临时关闭异常定时任务
  3. 限流、降级防止流量打垮服务
  4. dump 堆栈+内存文件,保留现场后重启

十、面试极简背诵版(一句话流程)

线上CPU飙升,我先通过top找到高CPU进程PID,再用top -H定位高CPU线程TID,转16进制后通过jstack抓取该线程堆栈,直接定位到具体类和代码行;同时用jstat排查是否是GC导致的CPU飙高,区分业务死循环还是内存问题。

十一、拓展:日常预防手段

  • 线上开启线程池监控、GC监控、CPU告警
  • 禁止裸 while(true) 循环,增加休眠和退出条件
  • 大集合遍历、复杂计算做数据量限制
  • 定时任务错开高峰、控制执行频率