内存泄漏是对象该回收却因强引用未释放而持续占用内存,溢出是内存空间耗尽导致OOM报错;泄漏为因、渐进恶化,溢出为果、突发崩溃。

内存泄漏和溢出不是“会不会发生”的问题,而是“什么时候暴露”的问题。掌握排查逻辑,比记住几个命令更重要——它让你在服务突然卡顿、OOM崩溃、GC频繁报警时,能快速定位到代码层的真实原因,而不是盲目重启或加内存。
先分清:泄漏 vs 溢出,本质不同
内存溢出(OutOfMemoryError)是结果,内存泄漏是常见诱因之一,但不是唯一原因。
- 溢出:堆/元空间/直接内存等区域空间耗尽,JVM抛出 OOM。可能是大对象、缓存未清理、线程数爆炸、类加载器堆积等导致。
- 泄漏:对象本该被回收,却因强引用链未断开而长期驻留堆中,持续占用内存,最终拖垮系统。典型如静态集合持对象、监听器未反注册、ThreadLocal未remove、连接未close等。
看现象:用对工具,避免误判
别一上来就 dump,先通过轻量级指标缩小范围:
- 用 jstat -gc 观察 GC 频率与回收效果:如果老年代持续增长且 Full GC 后几乎不降,大概率存在泄漏;若年轻代 Eden 区频繁满但 Survivor 区存活率高,可能是对象过早晋升。
- 查 jmap -histo 看实例数量突增的类:比如某 DAO 对象暴涨十倍,或大量 ByteBuf、Connection、Handler 实例堆积,就是线索。
- 结合应用日志和监控(如 Prometheus + JVM 指标),确认 OOM 是否集中在特定接口、定时任务或上线后某时段——这能帮你关联到具体代码模块。
抓证据:dump 分析要聚焦“谁在留着它”
拿到 heap dump 后,重点不是找“最多”的对象,而是找“不该活这么久”的对象及其引用链:
- 用 Eclipse MAT 打开 dump,选 “Leak Suspects” 报告,它会自动标记可疑对象及强引用路径;
- 手动分析时,右键可疑对象 → “Path to GC Roots” → 勾选 “with all references”,重点看哪条路径上有 static、ThreadLocal、ClassLoader 或未关闭的资源持有者;
- 注意区分“合理缓存”和“失控缓存”:比如 Guava Cache 有 size limit 和 expireAfterWrite,而自己写的 HashMap+Timer 若没做淘汰,就容易变成泄漏源。
修得稳:从编码习惯堵住常见漏洞
修复不能只改一行,要建立防御性习惯:
- 所有注册监听、添加回调的地方,确保有对应的反注册逻辑(尤其在 Activity、Fragment、Bean 销毁时);
- 使用 ThreadLocal 时,务必在 finally 块中 remove(),避免线程复用场景下数据残留;
- IO 资源(InputStream、Connection、Channel)优先用 try-with-resources;非标准资源(如 Netty 的 ByteBuf)要显式 release();
- 静态集合类加访问控制,提供 clear() 接口,并在必要时配合定时清理或弱引用包装(如 WeakHashMap 存 listener)。
不复杂但容易忽略——稳定系统的底气,往往藏在这些看似琐碎的引用管理里。

















