bigywater.tech
Java 内部原理 · 55

WeakReference 与 SoftReference 真实 GC 行为对比

书面说法是"Weak 一次 GC 就回收,Soft 内存不足才回收",这篇真实制造两种压力(正常 GC / 接近 OOM 的内存压力)去验证这句话,并且真实统计出内存压力下 SoftReference 的具体存活比例。

Weak 一次 GC 即空
真实验证:普通 System.gc() 后 WeakReference.get() 立刻变为 null,无需任何内存压力
Soft 压力下回收率 99.9%+
真实验证:-Xmx128m 下持续分配 10万个 1MB 对象,SoftReference 列表最终只剩个位数到几十个存活

上一篇:匿名内部类隐藏引用真实验证

真实验证:WeakReference 只需一次 GC

不需要任何内存压力,只要没有强引用,一次普通的 System.gc() 就真实能让 WeakReference.get() 变成 null。

WeakSoftReferenceDemo.java WeakReference<byte[]> weak = new WeakReference<>(new byte[1024]); System.gc(); System.out.println(weak.get() != null);
真实实测
GC 前 weak.get() != null: true
GC 后 weak.get() != null: false

真实结果符合 WeakReference 的定义:被引用对象没有任何强引用时,它连一次垃圾回收都撑不过,真实立刻被清空。

真实验证:SoftReference 在内存充足时挺过普通 GC

同样没有强引用,但换成 SoftReference,一次普通 GC 之后再检查。

真实实测
普通 GC 后 soft.get() != null: true

在内存没有压力的情况下,SoftReference 真实扛住了一次垃圾回收——JVM 判断"当前内存还够用",没有必要清理它。这也是 SoftReference 常被用来实现内存敏感缓存的原因:平时表现得几乎和强引用一样,只有真的缺内存时才会被牺牲。

真实验证:ReferenceQueue 拿到的是引用对象本身

对象被回收后,关联的 Reference 对象(不是被引用的对象本身)会被真实放进队列,方便做清理登记。

真实实测
对象被回收后,ReferenceQueue.poll() 拿到的引用是否是同一个: true

真实验证了一个容易搞混的细节:进入 ReferenceQueue 的是 WeakReference/SoftReference 这个"引用对象"本身,不是已经被回收、已经不存在的原始对象。这也是 WeakHashMap 这类结构能在后台清理失效 entry 的机制基础。

真实验证:内存压力下 SoftReference 的真实回收比例

用 -Xmx128m 限制堆大小,持续往 SoftReference 列表里塞 1MB 数组,直到内存真的紧张,看最终有多少存活。

真实实测(第一次运行)
JVM 最大堆内存: 128MB
内存压力下 softList 总数: 100000, 仍存活: 19, 已被回收: 99981
真实实测(第二次运行)
JVM 最大堆内存: 128MB
内存压力下 softList 总数: 100000, 仍存活: 49, 已被回收: 99951
真实数据的两个要点。第一,循环体本身在 128MB 堆下分配 10 万个 1MB 数组是绝对不可能全部存活的(总量远超堆容量),但整个循环真实跑完了,没有抛出 OutOfMemoryError——因为每次堆快满时,JVM 会先真实清理掉旧的 SoftReference 腾出空间,而不是直接报错。第二,两次真实运行"存活个数"不完全相同(19 和 49),说明 GC 触发的具体时机、以及"内存紧张到什么程度才清理 Soft"这件事受运行时状态影响,不是一个固定值,但共同规律很明确:几乎全部(99.9%+)都被真实回收了,只有最近分配的一小部分因为还没触发下一轮 GC 而侥幸存活。

分步演示:弱引用和软引用,真实什么时候会被清空

每一行都是真实运行这段代码得到的结果。

WeakReference真实只要对象不可达就会被清空;SoftReference真实要等到接近内存溢出才会被清空——两者"抗压能力"真实差很多
这一步新验证的引用状态

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17(-Xmx128m)得到,WeakReference/SoftReference 基础行为两次运行完全一致,压力测试的具体存活数量有波动但回收比例一致。
    • 和匿名内部类隐藏引用那篇一起看,能建立起"强引用/软引用/弱引用"这条完整的真实可达性链条。
    • 没有涉及:PhantomReference 的真实用途(必须配合 ReferenceQueue,get() 永远返回 null)、不同 GC 算法(G1 / ZGC)对 SoftReference 清理策略的真实差异——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号