CPU 飙到 100%,先别急着翻代码
Java 线上排查的四步法与三个坑
经典排查流程整理与修订 · 作者观点,仅供讨论
线上告警:某台机器 CPU 打满。很多人的第一反应是翻代码、猜哪里有死循环。我的观点是:**先定位,再推理,最后才看代码。**CPU 高只是症状,背后可能是业务线程在空转,也可能是 GC 在疯狂回收,两者的处理方向完全不同。
下面先给一套四步法(进程 → 线程 → 十六进制 → 栈),再讲我认为最容易踩的三个坑,最后是工具选择和线上现实。
01第一步:找到“最忙”的进程
用top按 CPU 排序,找到占用最高的进程 PID;如果机器上有多个 Java 进程,再用jps -l或ps -ef确认它是哪个应用。
top jps -l ps -ef | grep java | grep -v grep假设找到的进程 PID 是 5101。
02第二步:找到“最忙”的线程
进程是容器,真正吃 CPU 的是线程。有两种做法:
# 方式一:直接看线程级 CPU(更直观) top -Hp 5101 # 方式二:列出该进程的所有线程及其 CPU 时间 ps -mp 5101 -o THREAD,tid,time参数含义:-m显示进程下的所有线程,-p指定进程 ID,-o自定义输出列(这里是线程信息、线程 ID 和累计 CPU 时间)。
假设最忙的线程 TID 是 5102。
03第三步:线程 ID 转十六进制
jstack 输出里的线程号nid是十六进制,而top和ps给的是十进制,必须转换:
printf '%x\n' 5102 # 输出:13ee04第四步:用 jstack 找到具体代码
jstack 5101 | grep -A 30 'nid=0x13ee'输出里就能看到该线程的名字、状态和调用栈,沿着栈顶往下找到你们自己的业务类和行号,问题代码基本就定位出来了。
05坑一:把 GC 问题当成业务代码问题
如果第四步看到的线程名是GC task thread、VM Thread或G1 Concurrent开头,说明 CPU 是被垃圾回收吃掉的,翻业务代码是找不到答案的。
做法用jstat -gcutil 5101 1000 10观察各区使用率和 GC 次数、耗时;老年代长期居高、Full GC 频繁,就转向内存泄漏或堆配置问题,再用jmap -histo查对象分布。
判断CPU 高要先问:是业务线程在忙,还是 GC 在忙?这一步分岔,决定后面全部的方向。
06坑二:只抓一次快照
一次 jstack 只是某一瞬间的画面。如果线程刚好在做正常的计算,你会误判;真正的死循环或热点,则会在多次快照里停在同一处。
做法间隔几秒抓 3 次,比较同一个 TID 的栈。始终停在同一行附近,基本就是死循环或热点代码;栈每次都在变,更可能是整体负载过高,该考虑限流、扩容或优化算法。
07坑三:随手在生产上跑 jmap
原文列了一串常用工具:jps看进程,jstat看统计,jmap看内存映像,jinfo看配置。罗列没问题,但要补一条风险提醒:jmap -dump会让应用明显停顿,jmap -histo:live会触发一次 Full GC,高峰期在线上直接执行,可能把本来不严重的问题变成事故。
| 症状 | 优先工具 | 注意 |
|---|---|---|
| CPU 高,疑似业务死循环 | top -Hp+jstack | 连抓多次对比 |
| CPU 高,疑似 GC | jstat -gcutil | 先看 GC 趋势再动手 |
| 内存持续上涨、疑似泄漏 | jmap -histo、堆转储分析 | 转储会停顿,错峰或在摘流量的实例上做 |
| 确认启动参数是否生效 | jinfo | 只读,风险低 |
| 找 Java 进程 | jps -l | 容器内需有 JDK 工具 |
08线上现实:手工四步要会,但别只会手工
在容器和 K8s 环境里,会遇到几个实际问题:容器镜像里可能只有 JRE,没有 jstack;Java 进程的 PID 常常是 1;执行用户要和 Java 进程一致,否则会没有权限。
取理解四步法背后的原理,这样才看得懂任何排查工具在干什么,遇到没有工具的环境也能手工兜底。
舍线上优先用更成熟的工具,比如 Arthas 的thread -n 3一条命令就能列出最忙的几个线程和栈,或者用 async-profiler 做采样分析。更重要的是把工具提前放进镜像或运维平台,出事时才不用现装。
总结
CPU 飙高的排查,核心是一个顺序:先分清谁在忙(业务线程还是 GC),再用进程、线程、十六进制、栈四步缩小范围,多抓几次快照确认,最后才回到代码。排查完别忘了做两件事:把这次的过程写成 SOP,把对应指标加上监控告警,让下一次不再靠人在深夜手工敲命令。
说明:命令示例基于常见 Linux 与 JDK 环境,输出格式与参数可能因系统和版本不同而略有差异。