CPU 高、内存泄漏、GC 频繁、死锁排查思路
CPU高、内存泄露、GC频繁、死锁是Java 服务最常见的线上性能故障,且高度关联:内存泄漏 → 堆内存不足 → GC 频繁 → CPU 飙升。
一、必备工具
1. JDK 自带工具(无需额外安装,优先用)
jps:查看 Java 进程 PIDtop:Linux 查看系统 CPU / 内存top -Hp PID:查看进程内线程 CPU 占用jstack PID:导出线程栈(排查死循环、锁等待)jstat -gc PID 1000:实时查看 GC 情况jmap:导出堆内存快照(生产慎用,会暂停服务)jhat / MAT:分析堆快照
2. 可视化工具(推荐)
- MAT (Eclipse Memory Analyzer):分析内存泄漏神器
- Arthas:阿里开源,无侵入排查(生产首选)
- VisualVM:JDK 自带可视化监控
二、场景 1:CPU 使用率高(100% / 80%+)
1. 排查步骤:
(1) 定位CPU高的java进程 —— top命令
(2) 定位高耗CPU的进程 —— top -Hp 进程PID
(3) 将线程 ID 转为 16 进制 —— printf "%x\n" 线程TID
(4) 导出线程栈,定位代码行 —— jstack 进程PID > jstack.log
2. CPU 高 90% 根本原因(最常见)
- GC 线程疯狂执行(Full GC 频繁) → 直接导致 CPU 打满
- 代码死循环(while (true)、递归无退出)
- 大量线程阻塞 / 死锁(锁竞争激烈)
- 复杂计算 / 正则表达式 / 序列化(耗时逻辑)
- IO 密集型阻塞(网络 / DB) 导致线程暴涨
三、场景 2:GC 频繁(YGC/FullGC 飙升)
1. 实时查看 GC 情况
jstat -gc 进程PID 1000
重点看:
YGC/YGT:Young GC 次数 / 耗时FGC/FGCT:Full GC 次数 / 耗时(频繁 FGC 是严重问题)O:老年代使用率M:元空间使用率
2. GC 频繁判断标准
- FGC 几分钟一次 → 异常
- FGC 后内存不下降 → 内存泄漏
- YGC 每秒多次 → 新生代太小 / 短生命周期对象太多
3. 快速定位原因
- 老年代持续上涨 → 内存泄漏
- 突发上涨 → 大对象 / 流量突增
- 元空间满 → 动态生成类过多(CGLib / 反射)
四、场景 3:内存泄漏(OOM、堆只增不减)
1. 排查步骤
(1)确认内存泄漏
- jstat 观察:老年代持续上涨,FGC 后不下降
- 最终抛出:
OOM: Java heap space
(2)导出堆内存快照(生产谨慎!)
jmap -dump:format=b,file=heap.hprof 进程PID
✅ 安全方案:Arthas 导出 / 开启 JVM 自动 dump
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp
(3)用 MAT 分析泄漏
MAT 打开 heap.hprof 直接点击:Leak Suspects → 查看泄漏对象 → 定位引用链
2. 内存泄漏 最常见原因
- 静态集合缓存对象(static Map/List 只存不取)
- 线程池使用不当(核心线程 + 强引用对象)
- IO 流 / 连接未关闭(Socket、DB、文件流)
- ThreadLocal 未 remove(高频泄漏点)
- 外部缓存未清理(本地缓存无过期策略)
- 类加载器泄漏(热部署、自定义类加载器)
五、场景4:死锁问题排查
1. 线上死锁现象
- 接口卡死、不返回
- 服务线程全部阻塞,新请求处理不了
- CPU 不高、GC 正常,就是业务不动
- 线程一直处于 BLOCKED / WAITING
2. 排查步骤
(1) 定位 Java 进程 PID
jps
top
(2) 直接检测死锁
方式 1:jstack 一键看死锁
jstack 进程PID
拉到日志最底部,若有:Found one Java-level deadlock直接标明哪两个线程、哪两行代码、互相等待哪把锁。
方式 2:Arthas 在线排查
thread -b
直接检测并打印死锁线程,不用翻日志。
(3) 分析堆栈信息
从 jstack 日志看:
- 线程 1 持有锁 A,等待锁 B
- 线程 2 持有锁 B,等待锁 A定位到具体类、方法、代码行号。
(4) 临时止血
- 重启服务快速恢复业务
- 若有流量调度,切流量到其他节点
(5) 代码根治优化
破坏死锁四个条件之一即可:
-
统一锁顺序(最常用)所有线程按固定顺序获取锁,不会形成循环等待。
-
超时等待用
ReentrantLock.tryLock(时间)拿不到锁就放弃,不无限等待。 -
减少锁嵌套尽量不嵌套加锁,从源头避免请求保持。
-
锁粒度拆分把大锁拆成小锁,减少互相等待概率。
3. 死锁常见场景
- 两个线程嵌套获取两把不同锁,顺序相反
- 多张数据库事务更新顺序不一致,数据库行级锁死锁
- 多个
synchronized代码块嵌套混用 - 线程池多任务互相等待资源
六、三合一终极排查流程(生产通用)
第 1 步:先看系统负载:top → 定位高 CPU / 内存 Java 进程
第 2 步:看 GC 状态:jstat -gc PID 1000
- FGC 频繁 → 内存问题
- YGC 频繁 → 新生代太小 / 对象太多
第 3 步:看线程栈:jstack PID
- 大量 WAITING → 锁竞争 / 死锁
- 大量 RUNNABLE → 业务逻辑耗 CPU
第 4 步:看堆内存:MAT 分析 hprof → 找到泄漏对象与引用链
第 5 步:看代码:定位到具体类、方法、行,修复逻辑。
七、快速判断口诀
- CPU 高 + GC 频繁 = 内存泄漏 / 堆不足
- CPU 高 + GC 正常 = 代码死循环 / 密集计算
- 内存涨 + FGC 不下降 = 内存泄漏
- FGC 频繁 + 可用内存低 = 马上 OOM
- YGC 频繁 = 新生代小或短生命周期对象过多
八、生产优化建议
1. JVM参数优化
-Xms4g -Xmx4g (堆固定,避免扩容)
-XX:+UseG1GC (低延迟)
-XX:+HeapDumpOnOutOfMemoryError
2.代码规范
- ThreadLocal 必须
remove() - 流 / 连接必须关闭
- 本地缓存加过期、淘汰策略
3. 监控必备
- GC 次数监控
- 堆内存监控
- CPU 负载监控
更多推荐
所有评论(0)