内存泄漏是该回收的未回收,内存溢出是堆空间不足;需结合gc日志、堆快照、线程状态和代码逻辑分阶段排查:先区分oom类型,再分析gc日志特征,接着用jmap抓快照并mat分析,最后验证四类高频泄漏场景。

内存泄漏和内存溢出是 Java 应用线上故障的高频原因,但二者本质不同:内存泄漏是“该回收的没回收”,内存溢出是“堆空间不够用了”。排查时不能只看现象(如 OOM 报错),必须结合 GC 日志、堆快照、线程状态和代码逻辑,分阶段定位根源。
看现象:先区分是泄漏还是溢出
OOM 不等于内存泄漏。常见 OOM 场景中:
- java.lang.OutOfMemoryError: Java heap space —— 堆内存耗尽,可能是泄漏,也可能是瞬时大对象或配置过小;
- java.lang.OutOfMemoryError: Metaspace —— 元空间满,多见于动态类加载(如热部署、Groovy 脚本);
- java.lang.OutOfMemoryError: unable to create new native thread —— 线程数超限,常因线程池未复用或泄露;
- 频繁 Full GC 且老年代占用持续上升 → 强烈提示内存泄漏;
- 每次 GC 后堆内存几乎不下降 → 泄漏特征明显。
查日志:GC 日志是第一线索
启动参数加 -Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps,重点观察:
- Full GC 频率是否异常升高(如几分钟一次);
- 老年代使用量是否单向增长(GC 后仍居高不下);
- 每次 GC 回收量是否越来越小(说明存活对象在堆积);
- 是否存在 long GC pause(可能触发 STW 过久,间接暴露泄漏压力)。
抓快照:用 jmap 定位可疑对象
发现异常后,及时导出堆快照:jmap -dump:format=b,file=heap.hprof
- 按 Retained Heap 排序,找 top 几个大对象(尤其集合类、缓存、监听器);
- 看 dominator tree,识别谁在强引用大量对象;
- 用 Leak Suspects Report(MAT 自带),它会标记疑似泄漏链(如静态 Map 持有 Activity 实例);
- 对比多个快照(如间隔 10 分钟 dump 两次),观察对象数量/大小是否持续增长。
验代码:聚焦四类高频泄漏场景
90% 的内存泄漏集中在以下模式,需逐项核对:
-
静态集合类持有对象:如 public static Map
cache = new HashMap(); 未做 size 控制或 LRU 清理; - 未注销监听器/回调:注册了 BroadcastReceiver、Listener、TimerTask 却未在生命周期结束时 remove;
- ThreadLocal 使用不当:线程池中线程复用,ThreadLocal 变量未 remove(),导致对象长期滞留;
- 内部类持外部类引用:非静态内部类隐式持有 outer this,若被线程或静态容器引用,会阻止 outer 对象回收。











