Appearance
生产问题:Java 服务 CPU 飙升时怎么排查
CPU 飙升是线上最典型、也最容易让人慌的一类问题。
真正排查时,最怕的不是不会命令,而是一上来就乱抓现场,最后把最关键的信息弄丢。
它和“接口变慢但 CPU 不高”是两类完全不同的问题。
CPU 飙升通常意味着:
- 有线程在持续执行计算
- 有代码在自旋、死循环或高频重试
- 或者 GC 本身已经开始大量吃 CPU
先说结论
CPU 飙升时,最稳妥的顺序通常是:
- 先确认是不是 Java 进程导致
- 再确认是哪个线程在吃 CPU
- 再结合线程栈看它在干什么
- 如果怀疑内存或对象问题,再补抓 Heap Dump
更直接一点说,CPU 问题最重要的不是“先抓一堆文件”,而是先把最高 CPU 线程和对应线程栈对上。
因为大多数 CPU 故障,答案就在线程栈里。
一个典型故障现场
比如某个 Java 服务平时 CPU 在 20% 左右,某次发版后突然出现:
- 单机 CPU 长时间 90%+
- QPS 没明显上升
- 接口开始超时
- 线程池活跃数也很高
- GC 次数看起来增加,但又不是每次都在 Full GC
这时候最容易犯的错是:
- 一上来就抓 Heap Dump
- 一上来就怀疑数据库
- 一上来就直接重启
这些动作有时能缓解,但不一定能帮你保住真正现场。
一、先看进程
bash
jps -l -v先确认:
- 目标服务 PID
- 启动参数
这一步除了确认 PID,还要顺手看:
- 最近是否发版后 JVM 参数改过
- 是否有多个 Java 进程同时存在
- 问题是不是落在预期那个服务上
很多现场其实不是“主服务 CPU 高”,而是旁边某个消费者、任务进程、工具进程在占 CPU。
二、再看线程
如果要找具体是谁在吃 CPU,常见思路是:
- 用系统命令看高 CPU 线程
- 再把线程 ID 转换后到
jstack里定位
CPU 问题的核心现场,通常在线程栈,而不是 Heap Dump。
真正更有价值的问题是:
- 这个线程是在算什么
- 是正常业务计算、死循环、自旋等待,还是 GC 线程
- 是单个线程特别高,还是很多线程一起高
单线程高,常常更像:
- 死循环
- 自旋重试
- 某段算法逻辑异常
多线程都高,常常更像:
- 线程池大量执行热点任务
- 大规模序列化/反序列化
- 批量计算、批处理、索引构建
- 或者 GC 正在大量抢 CPU
三、先分清是业务线程高,还是 GC 在吃 CPU
这一步很关键,因为处理方向完全不同。
1. 如果是业务线程高
更要看线程栈是否集中在:
- 某个 while 循环
- 某个自旋 CAS 逻辑
- JSON/对象转换
- 正则匹配
- 排序、聚合、加解密、压缩
2. 如果是 GC 线程高
就要继续判断:
- 是 Young GC 太频繁
- 还是 Full GC 已经频繁
- GC 后堆是否明显回落
这时候 CPU 问题和内存问题往往已经绑在一起了。
四、什么时候抓 Heap Dump
如果你看到:
- CPU 高的同时伴随频繁 GC
- 怀疑对象堆积或内存问题
这时可以考虑:
bash
jmap -heap <pid>
jmap -dump:format=b,file=heap.hprof <pid>如果进程状态异常,也可能会遇到强制抓取场景。
但有一条经验很重要:
- 纯 CPU 问题优先抓线程栈
- 内存和 GC 明显异常时再补 Heap Dump
因为 Heap Dump 适合回答“对象为什么这么多”,不适合直接回答“哪个线程为什么把 CPU 打满了”。
五、线上最常见的根因画像
线上 CPU 飙升常见原因包括:
- 死循环
- 自旋重试过多
- 锁竞争激烈
- 线程池打满后业务反复重试
- 频繁 Full GC
再展开一点,特别高频的还有:
1. 代码死循环或边界条件错误
最典型,也最容易在发版后立即出现。
2. 自旋和重试策略过激
例如某个失败重试没有 sleep,没有退避,错误时瞬间把 CPU 打穿。
3. 热点数据导致大量重复计算
比如:
- 大对象序列化
- 大 JSON 解析
- 排序和聚合
- 报表或导出计算
4. GC 压力过大
这类通常会伴随:
- 分配速率高
- 对象生命周期不合理
- 本地缓存或集合堆积
5. 线程池把高开销任务同时放大
线程池本来是为了并发提速,但如果把 CPU 密集任务一口气放大到很多线程,也会把整机打满。
六、一个更实用的排查顺序
线上 CPU 飙升时,我通常按这个顺序看:
- 先确认是不是 Java 进程,以及哪台机器最明显。
- 看是单核拉满还是多核整体高。
- 找高 CPU 线程,并转到
jstack定位线程栈。 - 判断是业务线程、自旋线程还是 GC 线程。
- 如果伴随 GC 异常,再补 GC 日志和 Heap Dump。
- 最后再结合最近发版、流量变化、任务执行情况定根因。
这个顺序的重点就是:
- 先抓最接近真相的线程现场
- 再决定是否需要更重的堆分析
七、一个实用提醒
如果问题明确是 CPU 飙升,不要一开始就把重心全放在 Heap Dump。
因为:
Heap Dump 更适合看内存对象
CPU 更需要看线程执行栈
重启和大动作会直接破坏最重要的线程现场
一句话总结
CPU 飙升问题最关键的是先抓高 CPU 线程现场,再判断是否需要补看 GC、内存和对象。
线程栈通常比堆快照更接近答案。先搞清楚“是谁在烧 CPU”,问题往往就已经解决了一半。