java系统因gc引起的oom本质是gc持续工作但回收效果差,表现为full gc频繁、老年代逼近100%,根源多为内存泄漏或对象生命周期不合理,解决关键在于停止无效gc、定位真实占用者并切断增长链。

确认是不是 GC 型 OOM
别一看到 OOM 就调大堆内存。先看现象再动手:
- 监控中 Full GC 次数突增(如每分钟 ≥10 次),且每次耗时长、回收量少
- jstat -gc
显示老年代使用率长期 >95%,FGC 后仍无明显下降 - 服务响应变慢、线程卡顿、CPU 占用异常高(GC 线程争抢资源)
- 日志出现 java.lang.OutOfMemoryError: Java heap space 或 GC overhead limit exceeded
快速保留现场,避免重启丢线索
OOM 发生后第一件事不是重启,而是保现场:
- 确认 JVM 启动参数含 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump,确保 dump 已生成
- 若未开启自动 dump,且服务尚可响应,立即执行:jmap -dump:format=b,file=heap.hprof
- 同时抓一份 GC 日志:jstat -gc -h10
2000 (每 2 秒输出 10 行,持续 20 秒) - 注意:dump 过程会 STW,若服务已完全卡死,优先取已有 dump 文件
用 MAT 定位“真凶”,不只看大小
打开 dump 文件后,重点不是找最大的对象,而是找“不该长期存活却一直被强引用”的对象:
- 先看 Leak Suspects Report —— MAT 自动生成的可疑泄漏报告,往往直指核心问题类
- 再看 Dominator Tree —— 找出真正支配大量内存的对象(如某个静态 List、Cache 实例)
- 对可疑对象右键 → Path to GC Roots(排除弱/虚引用)→ 看谁在“死死抓住”它
- 特别关注:static 字段、ThreadLocal 变量、单例内部缓存、未关闭的监听器或回调
修复要切断增长源头,不止清一次缓存
很多修复只做“清空集合”,但没解决“为什么还会加进来”。必须从逻辑上阻断持续增长:
- 定时任务加载全量数据?→ 改为按需加载 + 设置缓存过期(如 Guava Cache 或 Caffeine)
- 静态 Map 不断 put?→ 加 size 限制 + LRU 策略,或改用软/弱引用
- Stream.toList() 或 myBatis 查询返回巨量 List?→ 强制分页、游标查询、或流式处理(forEach 而非 collect)
- ThreadLocal 未 remove?→ 在 finally 块或 filter 中显式调用 remove()
- 连接、流、ByteBuffer.allocateDirect() 未释放?→ 确保 try-with-resources 或手动 clean()
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











