内存泄漏是对象该回收却因强引用未断而持续占用堆内存,不报错但导致gc频繁、内存水位缓慢攀升,最终必然引发oom;oom是内存彻底耗尽的崩溃结果,可能由泄漏累积或其他瞬时内存需求超限直接触发。

内存泄漏不是“一下子”导致崩溃的故障,而是一个缓慢蚕食可用内存的过程;它本身不报错,但会持续抬高内存水位,最终把系统逼到无路可退——这时才触发内存溢出(OOM)。理解这个演变过程,关键在于看清“引用未断”如何一步步耗尽空间。
内存泄漏:对象该死却活了下来
Java中一个对象是否能被回收,取决于GC能否通过可达性分析判定它“不可达”。只要还有强引用连着它,哪怕业务逻辑早已不用,GC也绝不会动它。
- 比如静态集合一直存着历史请求数据,没清理也没设大小上限
- 比如注册了监听器但忘记反注册,监听器又持有了Activity或Service引用
- 比如ThreadLocal在线程池里复用,set后没调remove,旧值就一直卡在ThreadLocalMap里
这些场景下,对象没被释放,但程序还能跑——只是堆内存使用率会随时间 steadily 上升,GC频率变高、每次回收效果变差,老年代占用持续攀升。
从泄漏到溢出:三步挤压式恶化
内存泄漏不会静止不动,它会在运行中不断累积,形成“泄漏→GC压力↑→可用内存↓→新对象分配失败”的正反馈链:
- 第一阶段(平稳期):少量泄漏对象存在,GC Minor GC尚能及时清理年轻代,整体影响不明显
- 第二阶段(挤压期):泄漏对象越来越多,频繁晋升到老年代;Full GC开始变多,但因对象仍被强引用,回收无效;堆内存使用率长期 >85%
- 第三阶段(临界点):某次new对象时,JVM发现即使触发Full GC也腾不出足够空间——此时抛出java.lang.OutOfMemoryError: Java heap space
不是所有OOM都源于泄漏,但泄漏一定会走向OOM
内存溢出是结果,不是原因。它可能由以下任一情况直接触发:
- 一次性加载超大文件到内存(如1GB CSV全读进List)
- 递归过深导致栈溢出(StackOverflowError)
- 动态生成类过多撑爆元空间(Metaspace)
- 使用DirectByteBuffer未释放,耗尽直接内存
但如果是长时间运行的服务(如Spring Boot Web应用),出现OOM且堆内存曲线呈持续爬升趋势,那基本可以锁定是内存泄漏在作祟——因为只有长期驻留的对象,才会让内存使用量像滚雪球一样越积越大。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











