遇到outofmemoryerror应先判断是瞬时压力、配置偏低还是内存泄漏;通过jstat观察ou是否持续上升来区分,再用mat分析堆转储定位强引用链,结合业务代码修复资源持有问题。

遇到 java.lang.OutOfMemoryError: Java heap space,核心不是立刻加堆,而是判断:是瞬时压力大、配置偏低,还是对象长期堆积无法回收?真实泄漏往往藏在“本该释放却一直被强引用”的链条里。
看 GC 行为,快速区分泄漏与配置问题
用 jstat -gc <pid></pid> 持续观察几轮 Full GC 后的关键指标:
- 老年代使用量(OU)是否不降反升,甚至每次 GC 后都比前一次高?→ 强泄漏信号
- 年轻代 GC 频繁,但大量对象快速晋升到老年代,且长期不被回收?→ 对象生命周期设计不合理
- Metaspace、CodeCache 占用稳定,而堆持续上涨?→ 基本可排除类加载问题,聚焦堆内对象
抓堆快照,定位谁在“死死拽着内存”
确认泄漏倾向后,立即生成堆转储(heap dump):
- 若已配置
-XX:+HeapDumpOnOutOfMemoryError,OOM 后自动保存,直接分析 - 若未配置,且进程仍在运行:
jmap -dump:format=b,file=heap.hprof <pid></pid>(必要时加-F强制) - 用 MAT(Memory Analyzer Tool)打开,优先看 “Leak Suspects Report”,它会标出最可疑的持有者(如 static Map、ThreadLocal、未注销监听器)
- 对可疑对象右键 → “Merge Shortest Paths to GC Roots”,勾选排除弱/软/虚引用,重点看第 3~5 层的业务类引用链
结合代码验证引用是否合理
MAT 给出的是技术路径,需人工对照业务逻辑判断是否该持有:
- 发现大量
byte[]被IOUtils或某缓存工具类持有?检查流是否关闭、缓存是否设上限、是否共用静态缓冲区 - 看到成千上万个监听器或回调实例?确认注册后是否有对应
unregister或destroy,尤其注意事件总线、AOP 代理、动态注册场景 - 某个
ThreadLocal<map></map>持有大量数据?排查 Filter、Interceptor、异步任务中是否set后遗漏remove - Spring 应用中注意:@EventListener 在 prototype bean 上注册、@Scheduled 所在类被错误注入为 singleton、Feign Client 缓存未清理等典型陷阱
调优与修复必须同步落地
参数调整只是辅助,不能替代代码修正:
- JVM 参数:-Xms 与 -Xmx 设为相等,避免扩容抖动;老年代比例(-XX:NewRatio)按对象存活周期调整;慎用超大堆(如 >8GB),可能拉长 GC 停顿
- 代码层:大数据集必须分页或流式处理;缓存加 LRU 或 TTL;资源操作严格用 try-with-resources;避免 new 百 MB 级 byte[] 直塞堆中
- 上线后必须验证:用 jstat 或 Prometheus + JVM Exporter 跟踪老年代趋势,对比修复前后相同流量下的堆增长速率
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











