第一步:定性 --- 确认故障类型
先看监控大盘(Grafana / Prometheus / SkyWalking 等),确认故障属于哪一类:
- CPU 持续飙高
- 内存持续增长 / OOM
- 频繁 Full GC
- 接口响应慢 / 超时
- 线程阻塞 / 死锁
定性决定了后续排查方向,避免上来就盲猜。
第二步:定位异常进程
# 查看整机资源占用,找到异常 Java 进程 PID
top -c
# 或者只看 Java 进程
jps -l
如服务器4核,可以看到253533的进程cpu使用率高。

第三步:根据故障类型分流排查
场景一:CPU 飙高
# 1. 找到进程内哪个线程最耗 CPU
top -H -p <PID>
# 2. 将高 CPU 线程的 TID 转为十六进制
printf "%x\n" <TID>
# 3. 导出线程堆栈,搜索该 nid
jstack -l <PID> > thread.log
grep -A 30 "nid=0x<十六进制TID>" thread.log
判断要点:
- 堆栈停在业务代码且状态为
RUNNABLE→ 死循环、复杂计算、正则回溯 - 堆栈停在 GC 相关方法 → 频繁 GC 导致,转场景二
- 大量线程
BLOCKED→ 锁竞争,查waiting to lock和locked
💡 关键技巧:间隔 3~5 秒连续抓 3 次 jstack,如果某线程 3 次都停在同一行代码,说明确实卡死;如果一直在变,说明只是执行慢。
jstat -gcutil 是 Java 虚拟机(JVM)中用于监控垃圾回收(GC)状态的命令行工具,其输出以百分比形式展示各内存区域的使用率,便于快速判断 JVM 内存健康状况。该命令不会触发 GC,也不会挂起应用,非常适合线上环境实时观察。
各列含义及正常范围参考如下:
- S0 / S1(Survivor 区使用率):两个 Survivor 区交替使用,正常情况下一个为 0%,另一个在 0%~100% 之间波动。若两者同时高或长期为 0%,可能 Survivor 区配置过小或对象晋升过快。
- E(Eden 区使用率):正常应在 0%~95% 之间波动。若持续 >95% 且频繁 Young GC,说明对象分配速率过高或 Eden 区过小。
- O(老年代使用率):健康范围通常在 30%~70%。超过 70% 需警惕,超过 85% 且持续上升,极可能即将触发 Full GC。
- M(Metaspace 使用率):应稳定在 80%~95%。若长期 >90% 且持续增长,可能存在类加载泄漏或元空间配置不足。
- CCS(压缩类空间使用率):与 Metaspace 相关,通常与 M 列趋势一致,若异常升高也需关注类加载问题。
- YGC / YGCT(Young GC 次数与总耗时):次数随运行时间增长属正常,单次耗时一般 <0.01s 为良好。若 YGCT 增长过快,说明 Young GC 频繁或效率下降。
- FGC / FGCT(Full GC 次数与总耗时):理想状态为 0。若 FGC > 0 且 FGCT 持续增长,说明系统已出现内存压力,需立即排查。
- GCT(总 GC 耗时):等于 YGCT + FGCT,用于评估整体 GC 开销。
示例截图




场景二:内存泄漏 / 频繁 Full GC
# 1. 实时观察各区域使用率(百分比,直观)
jstat -gcutil <PID> 1000
# 2. 查看堆内存概要
jmap -heap <PID>
# 3. 查看哪些对象实例最多
jmap -histo <PID> | head -20
# 4. 导出堆快照(建议在低峰期执行,会触发 STW)
jmap -dump:live,format=b,file=heap.hprof <PID>

拿到 heap.hprof 后,用 Eclipse MAT 打开分析:
- 查看 Dominator Tree(支配树),找到占用内存最大的对象
- 查看 Leak Suspects(泄漏嫌疑),MAT 会自动分析可疑泄漏点
- 追踪 GC Root 引用链,定位是哪段代码持有了对象导致无法回收
常见根因:
- 静态集合(如
static List)不断往里塞对象 - 连接 / 流未关闭
- 线程池未销毁
- 一次性加载全表数据,未分页
- 缓存无上限、无过期策略
示例截图


使用mat分析

既然 MemoryLeakBug 占用了 402MB,它内部一定有个巨大的集合或数组。
- 在 MAT 的 Dominator Tree(支配树) 视图中,找到最大的
com.demo.bugs.MemoryLeakBug实例。


场景三:服务假死(CPU 不高但接口全超时)
# 导出线程堆栈
jstack -l <PID> > thread.log
# 统计线程状态分布
grep "java.lang.Thread.State" thread.log | sort | uniq -c
# 检测死锁
grep -i deadlock thread.log
如果大量线程处于 WAITING / BLOCKED,说明线程都在等资源(数据库连接、Redis、第三方接口、锁等),顺着堆栈找等待的具体资源即可。
第四步:Arthas 加速排查(推荐)
如果线上允许安装,Arthas 可以大幅简化排查:
# 安装并启动
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 常用命令
thread -n 3 # 直接看 CPU 最高的 3 个线程堆栈
thread -b # 一键检测死锁
trace 包名.类名.方法名 # 追踪方法内部各步骤耗时,定位慢接口
watch 包名.类名.方法名 # 监控方法入参、出参、异常
heapdump --live /tmp/heap.hprof # 导出存活对象堆快照
第五步:日志交叉验证
根据 jstack 堆栈中的代码行号,去业务日志中搜索对应时间点的日志,还原故障触发场景,确认是哪次请求、哪个参数导致的问题。
快速对照表
表格
| 故障现象 | 核心命令 | 常见根因 |
|---|---|---|
| CPU 飙高 | top -H -p + jstack |
死循环、正则回溯、频繁 GC |
| 内存持续增长 | jstat -gcutil + jmap -histo |
静态集合泄漏、连接未关闭 |
| 频繁 Full GC | jstat -gcutil + MAT 分析 |
老年代满、大对象、内存泄漏 |
| 服务假死 | jstack + grep BLOCKED |
死锁、锁竞争、外部资源超时 |
| 接口响应慢 | Arthas trace |
慢 SQL、缓存失效、下游超时 |
排查口诀
top 找进程 → top -H 找线程 → jstack 看代码 → jstat 看 GC → jmap 看内存 → MAT 找泄漏
掌握这套流程,绝大多数线上 Java 故障都能在几分钟内定位到根因。