内存泄漏本身不会直接导致溢出,但持续积累必然引发溢出;典型表现是堆内存持续上升、Full GC频繁且回收无效、对象实例数异常增长、非堆资源同步增加,最终因空间耗尽触发OOM。

内存泄漏本身不会直接导致溢出,但持续积累必然引发溢出。关键在于识别泄漏的早期信号,而非等到 OOM(OutOfMemoryError)发生才行动。
内存泄漏的典型表现特征
泄漏不是瞬间发生的错误,而是资源长期未释放形成的“慢性病”。常见迹象包括:
- 应用运行时间越长,堆内存使用量缓慢但持续上升,即使经历多次 GC 也难回落到初始水平;
- 频繁 Full GC,但每次回收效果差,老年代占用率只降不升或反复逼近阈值;
- 对象实例数异常增长,比如某类对象数量随用户操作线性增加,且无法被 GC 回收(可通过 MAT 或 JProfiler 查看支配树和引用链);
- 线程数、连接数、监听器、缓存项等非堆资源同步增长,暗示关联对象未解绑或关闭。
泄漏如何走向内存溢出
泄漏本身是“持有不该持有的引用”,而溢出是结果——当 JVM 无法为新对象分配足够连续空间时触发。这个过程有明确路径:
- 泄漏对象长期存活,逐渐挤占可用堆空间;
- GC 频次升高,STW 时间变长,应用响应迟滞;
- 最终 Eden 区无法容纳新对象,老年代填满,触发 OOM;
- 若泄漏涉及本地内存(如 DirectByteBuffer、JNI),还可能提前耗尽物理内存,触发系统级 kill。
定位泄漏的实用切入点
别一上来就 dump 堆,先缩小范围:
- 观察 GC 日志:关注每次 Full GC 后老年代剩余大小是否递增;
- 对比不同业务场景下的内存快照:比如登录后、执行某操作后、退出页面后,看哪些对象没被清理;
- 检查常见泄漏源:静态集合类、ThreadLocal 变量、未注销的监听器、未 close 的流/连接、缓存未设过期或上限;
- 用 jstat 实时监控:jstat -gc <pid> 1s 看各代变化趋势,比肉眼观察更可靠。
预防比修复更有效
多数泄漏源于设计或编码习惯,而非 JVM 问题:
- 优先使用弱引用(WeakReference)或软引用(SoftReference)管理缓存;
- 所有注册监听器的地方,确保在生命周期结束时反注册;
- 资源类(InputStream、Connection、Channel 等)严格配合 try-with-resources 或 finally 显式释放;
- 避免在 ThreadLocal 中存放大对象,使用后调用 remove();
- 对静态容器做容量控制与自动清理机制,比如用 LRUCache 替代 HashMap。
不复杂但容易忽略。真正的问题往往藏在“应该被回收却一直活着”的对象背后。

















