内存排雷:软引用里的对象被 GC 回收后,软引用自身会变成“内存刺客”吗?

张开发
2026/4/13 6:53:53 15 分钟阅读

分享文章

内存排雷:软引用里的对象被 GC 回收后,软引用自身会变成“内存刺客”吗?
内存排雷软引用里的对象被 GC 回收后软引用自身会变成“内存刺客”吗在 Java 进阶之路上很多开发者都背过这样的八股文“软引用SoftReference关联的对象在系统将要发生内存溢出异常前会被垃圾回收器回收掉。”但如果你多往深处想一步往往会得出一个让人冷汗直冒的推论如果软引用里面包装的那个真实对象Referent被 GC 回收了那这个SoftReference对象本身呢它是不是有一条通往 GC Root 的强引用它是不是永远不会被自动回收今天我们就来扒开这层迷雾看看 JVM 到底是怎么处理这些“空壳子”的。一、 拨开迷雾“包装盒”与“内部物品”的殊途异路为了看清底层的生杀大权我们必须把代码SoftReferenceA sr new SoftReference(new A());拆解为两个独立的命运线。1. 内部物品的命运为什么 A 对象会死当 JVM 的堆内存快要被撑爆时垃圾回收器GC会进行绝地反击。它顺着引用链扫描发现A对象的身上没有任何“铁链”强引用保护仅仅只剩下一根“细绳子”软引用牵着它。为了自保避免 OOMJVM 只能“挥泪斩马谡”把A对象无情地清理掉腾出宝贵的空间。此时如果你再去调用sr.get()拿到的一定是null。2. 包装盒的命运为什么SoftReference依然活着SoftReference本身也是 Java 堆内存里的一个普普通通的对象包装盒。变量sr通常存在于当前线程栈帧的局部变量表里或者作为一个强引用属性存在于某个全局的Map或List中。从 GC Root 到sr指向的这个SoftReference对象本身是一条坚不可摧的【强引用】链条只要你的业务代码还在往下跑或者这个缓存结构还没有被销毁GC 看到SoftReference身上挂着强引用的铁链就绝对不敢动它一根汗毛。二、 进阶思考悄然而至的“空壳垃圾”内存泄漏既然推导到了这一步就必然会引出一个极其现实且致命的工程问题。假设你为了提升性能用HashMapString, SoftReferenceImage做了一个巨大的本地缓存里面存了 10 万个高斯模糊处理后的图片对象。 现在内存告急里面包装的 10 万个Image对象全被 GC 合法回收了成功化解了 OOM。但是结果却留下了 10 万个装满null的SoftReference空壳子对象它们被外层的HashMap强引用死死牵着永远死不掉。虽然单个空壳不大但积少成多这不仅浪费了内存还会拖慢后续的 Hash 冲突查找效率。这就是一种极其隐蔽的变相内存泄漏三、 绝地翻盘JVM 官方的终极清洁工ReferenceQueueJava 官方在设计引用机制时早就预判了你的预判。为了解决这个恶心的“空壳堆积”问题他们专门配发了一个终极武器引用队列ReferenceQueue。如何使用这把终极武器在创建软引用的时候你只需要多传一个参数把它和一个ReferenceQueue绑定起来ReferenceQueueAqueuenewReferenceQueue();SoftReferenceAsrnewSoftReference(newA(),queue);奇迹发生的过程自动入队当 JVM 决定把真正的A对象回收掉的时候它内部会极其智能地、自动把这个已经空掉的SoftReference对象空壳子塞进你指定的那个ReferenceQueue里面主动清理你的程序只需要开启一个轻量级的后台守护线程死死盯着轮询这个队列。斩断强引用只要看到队列里吐出了一个空壳SoftReference你的代码就可以立刻拿着这个引用去你的大缓存Map里把这个没用的键值对彻底删掉即斩断对空壳的最后一道强引用。一旦你把它从缓存结构里删掉这个SoftReference空壳也就失去了最后的强引用保护。在下一次 GC 到来时它就会随着它曾经守护的物品一起被彻底清理得干干净净。总结软引用对象本身确实是由强引用保护的不会随其内容物一起被自动回收。但在工业级架构设计中我们绝不能放任这些“空壳”野蛮生长。通过配合使用ReferenceQueue我们就能形成一个闭环既享受了软引用带来的弹性内存释放又利用引用队列完成了包装盒的自动清道夫工作。这是构建高性能本地缓存必须要掌握的核心底层心法。

更多文章