🗂️ 目录
- 前言:理论掌握只是起点,定位能力才是核心
- 全局排查模型:三步法
- 1️⃣Full GC 频繁触发:老年代压力过大
- 2️⃣ OOM 爆炸:元空间泄漏 or 缓存未清理
- 3️⃣ CPU 飙升却不是 GC:线程阻塞或热方法失控
- 4️⃣ Redis 连接池打满:小问题大代价
- 5️⃣ 死锁诊断:必备 jstack 武器
- 6️⃣ GC 抖动严重:年轻代设置不合理
- 7️⃣ 堆外内存泄漏:被忽视的 DirectBuffer
- 8️⃣ JIT 编译失效:小细节大瓶颈
- 9️⃣ GC 长时间 STW:暂停时间不可控
- 🔟 工具组合套件:不止是命令更是组合拳
- 📌 实战总结:你该构建的不是记忆,而是体系
- 🚀 下一篇预告:第九篇《JVM 即时编译机制详解》
前言:理论掌握只是起点,定位能力才是核心
如果说掌握 JVM 内存模型与 GC 原理是性能优化的“基础体能”,那么系统性地排查 JVM 性能瓶颈,才是真正能打硬仗的核心能力。 本文将结合真实项目中的排查场景,通过 10 个高频实战问题,帮你理清:
- 问题出现的底层原理
- 如何利用工具快速定位
- 排查思路是否体系化
- 如何规避同类问题再次发生
全局排查模型:三步法
JVM 层性能问题,一般可抽象为以下三步分析模型:
▶ 现象定位:是否是 GC / OOM / 卡顿 / 死锁 / CPU 异常
▶ 数据采集:jstat、jmap、jstack、GC 日志、Arthas 等
▶ 根因分析:内存泄漏?线程阻塞?资源未释放?配置不合理?
无论你面对的是线上事故,还是慢请求报警,这个三步法都能迅速组织你的思路。
性能问题 10 连击实战
1️⃣Full GC 频繁触发:老年代压力过大
现象:
- 日志中频繁出现 Full GC 记录,STW 停顿显著。
- 应用响应时间骤升。
排查方式:
- jstat -gcutil PID 1000 5 查看 OU(Old Used)占比是否持续高位。
- 分析 GC 日志,查看 Full GC 的触发频率与耗时。
常见原因:
- 老年代对象过多,晋升频繁(可调大 -XX:SurvivorRatio)。
- 内存回收不及时,内存泄漏或大对象直接进入老年代。
2️⃣ OOM 爆炸:元空间泄漏 or 缓存未清理
常见异常:
java.lang.OutOfMemoryError: Metaspace
或
java.lang.OutOfMemoryError: GC overhead limit exceeded
可能原因:
- 类频繁加载却未卸载(典型于 SPI 或动态生成类场景,如 CGLIB)。
- 缓存未设置过期时间,堆持续膨胀。
解决建议:
- 增大元空间:-XX:MaxMetaspaceSize=256m。
- 对动态类使用 WeakReference 或确保卸载条件。
- 定期清理缓存(如 Guava Cache)。
3️⃣ CPU 飙升却不是 GC:线程阻塞或热方法失控
现象:
- CPU 占用长期 100%,GC 日志无异常。
排查方式:
top -Hp <pid> # 找出高 CPU 线程
jstack <pid> # 查看线程栈
常见原因:
- 死循环或大量计算(可定位热方法)。
- 锁竞争过高,线程频繁阻塞。
4️⃣ Redis 连接池打满:小问题大代价
现象:
- 应用响应超时,线程堆栈显示阻塞在 redis.getConnection()。
原因剖析:
- 连接池默认配置过小。
- 未正确关闭连接,资源泄漏。
优化建议:
- 增大连接池:maxTotal、maxIdle。
- 使用 try-with-resource 自动关闭连接。
5️⃣ 死锁诊断:必备 jstack 武器
jstack <pid> | grep -A20 "Found one Java-level deadlock"
死锁典型模式:
- A 等 B 的锁,B 等 A 的锁。
- Synchronized 和数据库锁混用场景最危险。
6️⃣ GC 抖动严重:年轻代设置不合理
现象:
- Minor GC 频繁,每次暂停虽短,但整体 TPS 明显下降。
检查项:
- eden 区域太小。
- Survivor 区太小,导致频繁晋升。
调优建议:
- -XX:NewRatio=2 控制新老年代比例。
- -XX:SurvivorRatio=6 优化 Eden 与 Survivor 比例。
7️⃣ 堆外内存泄漏:被忽视的 DirectBuffer
典型异常:
java.lang.OutOfMemoryError: Direct buffer memory
常见原因:
- Netty、NIO 分配了大量堆外内存,但未及时释放。
建议:
- 调整堆外内存限制:-XX:MaxDirectMemorySize
- 定期调用 System.gc() 强制回收(仅限测试)。
8️⃣ JIT 编译失效:小细节大瓶颈
现象:
- 方法多次执行但未触发 JIT 编译。
- 程序热启动后,性能没有提升。
诊断方式:
-XX:+PrintCompilation
可能原因:
- 方法体太大、递归调用、异常处理复杂等。
9️⃣ GC 长时间 STW:暂停时间不可控
典型表现:
- 一次 GC 停顿达 5s~10s,甚至触发服务降级。
根因可能是:
- CMS GC remark 阶段 STW。
- G1 的 Mixed GC 调优不足。
建议:
- 切换至 ZGC / Shenandoah 这类低暂停收集器。
- 或通过 -XX:MaxGCPauseMillis 精细化配置。
🔟 工具组合套件:不止是命令更是组合拳
jstat | 实时 GC 状态 |
jmap | 导出堆 dump、统计 histogram |
jstack | 查看线程状态、死锁排查 |
Arthas | 在线诊断神器 |
MAT | 深度内存分析、泄漏跟踪 |
👉 实战建议:配合使用才最强大。诊断时不要只靠一个命令,要构建工具链与排查路径。
📌 实战总结:你该构建的不是记忆,而是体系
与其死记参数和现象,不如掌握背后的“排查模式”和“性能地图”:
- 全局模型:资源 -> 配置 -> 行为 -> 报错。
- 工具分工:实时监控、快照导出、代码热插拔。
- 联动思维:GC 不一定是根因,可能是症状。
只要你构建出一套属于自己的排查体系,面对任何线上问题,都会更有底气。
🚀 下一篇预告:第九篇《JVM 即时编译机制详解》
下一篇我们将深入探讨 JVM 的 JIT 编译原理,包括 C1/C2 编译器、热点探测、逃逸分析以及生产环境如何诊断 JIT 编译引发的性能变化。
如果你觉得这篇文章对你有启发,欢迎 点赞👍、收藏⭐、关注✅,你的支持是我持续更新高质量 JVM 系列的最大动力!
如有实际问题,也欢迎评论区交流,我会持续整理典型问题加入专栏!
评论前必须登录!
注册