CPU高、内存泄露、GC频繁、死锁是Java 服务最常见的线上性能故障,且高度关联:内存泄漏 → 堆内存不足 → GC 频繁 → CPU 飙升。

一、必备工具

1. JDK 自带工具(无需额外安装,优先用)

  • jps:查看 Java 进程 PID
  • top: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 负载监控
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐