一、CPU 飙高排查流程
线上遇到 CPU 飙高,建议先定位进程,再定位线程,最后回到代码。
- 先用 top 看一眼:找到占用 CPU 最高的进程 PID。
top -Hp PID:查看该进程里哪个线程 TID 占用最高。
printf "%x\n" TID:把线程 TID 转成十六进制,方便后续搜索。
jstack PID 打印线程栈,搜索十六进制 tid,定位具体代码:
- 大量
RUNNABLE:死循环、密集计算、正则回溯、循环 IO、频繁 GC。
- 大量
WAITING/BLOCKED:锁竞争、死锁。
- 辅助命令
jstat -gcutil PID 1000:观察 GC 频率。如果 GC 频繁导致 CPU 高,就转入内存问题排查。
- 定位代码后:重点检查是否存在无限循环、大数据量遍历、频繁创建/销毁对象、死锁。
常见原因:死循环、大量正则、频繁 Full GC、大量线程上下文切换、锁自旋。
二、OOM / 内存泄漏排查流程
OOM 是内存用完、分配不出新对象;内存泄漏则是对象虽不再被业务使用,但一直被强引用持有,慢慢堆积,最终触发 OOM。
- 先看异常日志,分清 OOM 类型:
Java heap space:堆内存满,最常见。
Metaspace:元空间溢出,常见于类加载过多、动态生成类。
unable to create new native thread:线程数超限。
- 配置启动参数,开启自动 dump:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xxx.hprof。这样一旦 OOM 会自动导出堆快照,避免现场丢失。
- 拿到 hprof 文件后,用 MAT(Memory Analyzer Tool) 分析:
Leak Suspects(泄漏嫌疑报告):优先看这个。
- 支配树(Dominator Tree):查看哪些大对象占内存、关联的 GC Roots。
- 查看对象引用链:找出谁持有这个对象,导致 GC 无法回收。
- 必要时用
jmap -dump:format=b,file=xxx.hprof PID 手动 dump 堆。线上尽量低峰执行,因为会触发 STW。
- 用
jstat -gc 持续观察 GC 曲线:
- 每次 YGC 后内存仍持续上涨,GC 回收不掉 → 内存泄漏。
- 一次性产生大量对象,一次 GC 后内存回落 → 瞬时大对象,不是泄漏。
- 常见泄漏点:
- ThreadLocal 没有 remove。
- 静态集合一直 add,没有淘汰机制。
- 长生命周期集合持有短生命周期对象。
- 流、连接、IO、线程池等资源忘记关闭。
三、区分重点(面试必说)
- CPU 高不等于内存问题:可能是死循环,也可能是频繁 Full GC,GC 线程吃满 CPU。
- 内存泄漏:渐进式,运行时间越久内存越高,每次 GC 释放很少,几天后慢慢 OOM。
- 瞬时 OOM:一次性加载超大 List、大文件,属于内存溢出,不是泄漏。
四、精简背诵版(短回答)
CPU 高:top 定位 PID → top -Hp 定位线程 → 转十六进制 tid → jstack 看线程栈,区分死循环 / 锁竞争 / 频繁 GC。
OOM / 内存泄漏:先看日志区分 OOM 类型;开启 OOM 自动 dump;用 MAT 分析 hprof,看泄漏报告、支配树、引用链;配合 jstat 观察 GC 曲线,判断是泄漏还是瞬时大对象;常见泄漏点包括 ThreadLocal、静态容器、未释放资源。
高频追问
Q:dump 堆的时候有什么风险?
A:dump 会触发 STW,堆越大停顿越久。生产环境尽量低峰操作,优先配置自动 dump,不要频繁手动 dump。
Q:MAT 看支配树是干嘛的?
A:支配树表示:如果一个对象被回收,它下面关联的对象也都能被回收,用来快速定位内存大户。
Q:频繁 Full GC,但还没有 OOM,怎么排查?
A:同样用 jstat 与 jmap 判断,看是不是内存泄漏、瞬时大对象或堆参数不合理。
|