对象引用不当是oom最常见诱因,本质是强引用未断致本该回收的对象滞留堆中;需通过gc roots强引用链、老年代使用率、gc日志等指标快速定位泄漏源,并用mat分析堆快照聚焦静态集合、threadlocal等五类高频泄漏点。

对象引用不当是内存溢出(OOM)最常见诱因之一,本质是本该被回收的对象因强引用未断而滞留堆中,持续累积直至耗尽空间。排查核心不是“谁占得多”,而是“谁不该活还活着”——重点看 GC Roots 到对象的强引用链。
看堆内存使用趋势,快速判断是否泄漏
先不等 OOM 发生,用基础指标初步定位:
- 老年代使用率长期 >90%,且每次 Full GC 后仅下降 2%~5%,说明大量对象无法回收
- GC 日志频繁出现 GC overhead limit exceeded 或单次 Full GC 耗时超 1 秒
- 服务重启后,内存占用曲线在几小时内就快速回升至高位,而非缓慢爬升
抓堆转储并用 MAT 找强引用源头
确认异常后立即导出堆快照,再精准分析:
- 运行 jmap -dump:live,format=b,file=heap.hprof
(或配置 -XX:+HeapDumpOnOutOfMemoryError自动触发) - 用 MAT 打开 hprof 文件,先看 Leak Suspects Report,它会标出前几个高风险对象组
- 切换到 Dominator Tree,按 Retained Heap 排序,重点关注
byte[]、HashMap、ArrayList及业务缓存类 - 对可疑对象右键 → Path to GC Roots → exclude all weak/soft references,强引用链终点往往就是泄漏根:静态字段、ThreadLocal、未注销监听器或线程池中未清理的 Runnable
聚焦五类高频引用泄漏点,针对性检查
不用全盘扫代码,优先验证以下典型场景:
-
静态集合:如
private static Map<string object> cache = new HashMap();</string>—— 检查是否有过期清理逻辑,或改用WeakHashMap/Caffeine -
ThreadLocal 未清理:Web 应用中请求线程往
ThreadLocal<byte></byte>存数据后未调remove(),线程池复用导致内存越积越多 -
资源未关闭:
InputStream、Connection、Statement等 —— 必须用 try-with-resources,避免 finally 中漏写 close - 监听器/回调未注销:动态注册事件总线、GUI 组件或 Spring 监听器后,缺少对应 unregister 方法或未执行
- 大对象直接持有上下文:比如异步任务中把整个 Request 或 Service 实例塞进队列,但队列长期堆积未消费
修复后验证与预防
改完代码不能只靠重启观察,要闭环验证:
- 压测相同流量路径,对比老年代使用率和 Full GC 频次是否明显下降
- 在关键对象构造/销毁处加日志,确认生命周期符合预期
- 上线前开启
-XX:+PrintGCDetails和-XX:+HeapDumpBeforeFullGC,便于后续快速捕获异常现场 - 将 MAT 分析流程纳入 CI/CD,对每日构建生成的堆快照做 Retained Heap 基线比对










