内存泄漏是因,内存溢出是果;泄漏导致堆内存持续增长、gc无效,最终触发outofmemoryerror。需通过jvm参数(如-xx:+heapdumponoutofmemoryerror)、jstat、mat和jstack协同定位泄漏对象及强引用链。

内存泄漏和内存溢出的关系要理清
内存泄漏不是直接导致程序崩溃的“罪魁”,但它像慢性病一样持续蚕食堆内存。当泄漏对象长期无法被 GC 回收,可用堆空间不断减少,最终在某次对象分配时触发 OutOfMemoryError: Java heap space——这就是内存溢出(OOM)。换句话说,泄漏是因,溢出是果;定位溢出点只是表象,真正要找的是那些本该回收却一直被意外引用的对象。
从 JVM 启动参数开始埋点
没有运行时数据,分析就是盲人摸象。启动应用时务必加上关键 JVM 参数:
- -XX:+HeapDumpOnOutOfMemoryError:OOM 发生时自动生成 .hprof 堆转储文件,这是后续分析的基石
- -XX:HeapDumpPath=/path/to/dumps/:指定 dump 文件保存路径,避免权限或磁盘满导致生成失败
- -XX:+PrintGCDetails -Xloggc:gc.log(JDK 8)或 -Xlog:gc*:gc.log(JDK 11+):记录 GC 频率、耗时、前后堆大小,观察是否出现“GC 后老年代持续增长”等泄漏迹象
用工具链分层排查泄漏源头
单靠日志只能发现问题存在,定位具体类和引用链需要工具协同:
-
jstat 实时观测:执行 jstat -gc
5s 查看每 5 秒的 GC 统计。若 Old Gen 使用量随时间单向攀升,且 Full GC 后回收极少,高度提示泄漏 - MAT(Eclipse Memory Analyzer)分析 dump:打开 .hprof 文件后,优先查看 Leak Suspects Report,它会自动识别疑似泄漏的支配树(Dominator Tree);再深入 Retained Heap 排序,找占用最大的对象类型;右键对象 → Path to GC Roots(排除弱/软引用),看清谁在强引用它
-
jstack 辅助验证:若怀疑是线程未释放(如 ThreadLocal、静态线程池),用 jstack
检查活跃线程数是否异常增长,或是否存在长时间等待、阻塞的线程持有大量对象
常见泄漏模式与自查清单
很多泄漏并非逻辑复杂,而是疏忽所致。上线前可快速核对:
- 静态集合类(static Map/List)是否无限制 put,又没配套清理机制?尤其缓存场景
- 监听器、回调、EventBus 等注册后,是否在对象销毁时显式 unregister?Android 中 Activity 泄漏多源于此
- ThreadLocal 变量是否在业务结束后调用 remove()?子线程复用线程池时极易累积
- 数据库连接、IO 流、网络 Socket 是否都放在 finally 或 try-with-resources 中关闭?资源未释放常连带其关联对象无法回收











