内存溢出(OOM)是申请不到内存,内存泄漏是该回收的对象因强引用未被回收;泄漏长期积累几乎必然导致OOM,排查应通过jstat监控OU/EU/S0U趋势、jmap -histo:live初筛异常对象、MAT分析GC Roots定位静态集合、未关闭资源、监听器未注销等典型泄漏源。

内存溢出(OOM)和内存泄漏不是一回事:溢出是“申请不到内存”,泄漏是“该回收的没回收”。泄漏不一定会立刻引发溢出,但长期泄漏几乎必然导致溢出。排查时不能等 OOM 报错才动手——那时往往已错过干预窗口。
看 GC 行为趋势,而不是等异常
用 jstat -gcutil <pid> 2000 每两秒刷新一次,盯住三个关键指标:
- OU(老年代使用率):单向持续上涨、Full GC 后只回落一点点 → 强烈提示泄漏
- EU(Eden 区使用率):Young GC 后下降幅度越来越小 → 对象在年轻代“滞留”,可能被意外强引用
- S0U/S1U(幸存区使用率):长期接近 100% 且无明显波动 → 对象反复躲过 Young GC,可能晋升异常或被静态持有
轻量筛查存活对象,避开 dump 风险
别一上来就 jmap -dump——大堆会卡顿几秒甚至几十秒。优先执行:
- jmap -histo:live <pid>(注意必须加 :live)
- 重点关注:#instances(实例数)、bytes(总字节)、class name(类名)
- 典型泄漏信号:
• 自定义类(如com.example.OrderCache)实例达数十万以上
•byte[]、char[]占比异常高(缓存未清理、流未关闭、Base64 解码残留)
• 大量HashMap$Node或ArrayList(集合无节制膨胀)
定位强引用链,揪出“不肯放手”的对象
确认可疑类后,再用 MAT(Memory Analyzer Tool)分析 heap dump:
立即学习“Java免费学习笔记(深入)”;
- 打开 dump 文件 → “Leak Suspects Report” 快速识别高风险对象
- 右键可疑类 → “Merge Shortest Paths to GC Roots” → 勾选 exclude weak/soft/phantom references
- 重点看路径中是否含:
•java.lang.Thread(线程局部变量未清理)
•static字段(如静态 Map、缓存、监听器)
•java.util.Timer或java.util.concurrent.ScheduledThreadPoolExecutor(定时任务持引用)
结合代码常见场景快速验证
90% 的泄漏集中在以下几类,可直接检查对应代码:
-
静态集合类:如
public static List<User> cache = new ArrayList<>();→ 生命周期与 JVM 一致,永不释放 - 资源未关闭:InputStream、Connection、Socket 等未用 try-with-resources 或 finally 关闭 → 不仅堆内存泄漏,还耗尽系统句柄
- 监听器/回调未注销:GUI 组件、事件总线注册后未反注册 → 被 UI 或框架强引用,无法回收
-
ThreadLocal 使用不当:在线程池中未调用
remove()→ 线程复用导致旧数据一直挂着 - 内部类持有外部类引用:非静态匿名内部类(如 new Thread() {...})隐式持有 this → 若线程长期运行,外部 Activity 或 Service 无法释放


















