mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2316 字
6 分钟
弱引用与引用强度(Weak / Soft / Phantom Reference)
2026-06-13

一、为什么需要不同强度的引用#

写一个图片缓存:加载过的图片放进 Map,下次直接取。但如果缓存越积越大,内存吃紧了怎么办?你希望 GC 在内存不够时自动回收缓存里那些”暂时不用”的图片——可一旦你拿着强引用指向它们,GC 绝对不会动。

这就是矛盾:强引用太强,不允许回收;但有些场景恰恰需要”允许回收的引用”

典型需求包括:

  • 缓存:内存充裕时保留,内存紧张时让 GC 回收
  • 规范映射(Canonicalizing Map):用 WeakHashMap 关联对象的元数据,对象本身被回收后映射条目自动消失
  • 资源清理:对象被 GC 回收之前执行清理逻辑(关闭文件句柄、释放 native 内存)

一句话:我们需要分级的引用强度——从”绝对不回收”到”随时可回收”再到”回收前通知我”,逐级递减。

二、现实类比#

机场登机有三种乘客:VIP 乘客持有头等舱登机牌,无论如何都能上飞机;普通乘客持有经济舱票,正常情况下能上,但航班超售时可能被挤掉;候补乘客只有等待名单上的名字,有空座才轮到,没座就算了。

酒店也一样:已入住的客人(强引用),房间不会被收回;预订了但可以超售让步的客人(软引用),酒店满房时可能被通知另找住处;等候名单上的人(弱引用),只是个名字,有房才联系你,没房你也不存在。

对应到引用类型:强引用是 VIP,软引用是普通乘客,弱引用是候补,虚引用是退房后帮你收拾房间的人

三、核心思想#

引用强度形成一条清晰的谱系:Strong > Soft > Weak > Phantom。每种强度对应 GC 不同的处理策略。

flowchart TD A["对象被创建"] --> B{"存在强引用?"} B -- 是 --> C["不可回收\n(强可达)"] B -- 否 --> D{"存在软引用?"} D -- 是 --> E["内存不足时回收\n(软可达)"] D -- 否 --> F{"存在弱引用?"} F -- 是 --> G["下次 GC 即回收\n(弱可达)"] F -- 否 --> H{"存在虚引用?"} H -- 是 --> I["已回收,入队通知\n(虚可达)"] H -- 否 --> J["完全不可达\n可最终回收"]

3.1 强引用(Strong Reference)#

强引用是默认行为。只要有一条强引用链从 GC Roots 到达对象,这个对象就绝对不会被回收。即使是内存溢出(OOM),JVM 也不会释放强引用对象。

Object obj = new Object(); // 强引用
obj = null; // 只有断开强引用,对象才可能被回收

3.2 软引用(Soft Reference)#

软引用在内存充裕时保留对象,内存不足时才由 GC 回收。JVM 保证在抛出 OOM 之前清除所有软可达对象。它是最适合做缓存的引用类型。

import java.lang.ref.SoftReference;
// 创建软引用
SoftReference<byte[]> cache = new SoftReference<>(new byte[1024 * 1024]);
// 使用时需要检查是否已被回收
byte[] data = cache.get();
if (data == null) {
// 已被 GC 回收,需要重新加载
data = new byte[1024 * 1024];
cache = new SoftReference<>(data);
}

3.3 弱引用(Weak Reference)#

弱引用比软引用更弱——下一次 GC 运行时就会被清除,不管内存是否紧张。典型用途是规范映射,比如 Java 的 WeakHashMap:键是弱引用,键对象被回收后整个条目自动移除。

import java.lang.ref.WeakReference;
WeakReference<Object> ref = new WeakReference<>(new Object());
System.gc(); // GC 之后 ref.get() 大概率返回 null
System.out.println(ref.get()); // null

3.4 虚引用(Phantom Reference)#

虚引用是最弱的引用——get() 永远返回 null,你无法通过虚引用访问对象。它的唯一作用是:对象被 GC 确定回收后,虚引用会被加入引用队列(ReferenceQueue),让你在对象真正消失前执行清理逻辑。

虚引用 vs 终结器(finalizer):终结器的问题是对象可以在 finalize() 中”复活”自己,而虚引用无法复活对象,更安全。

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> ref = new PhantomReference<>(new Object(), queue);
// 永远返回 null
System.out.println(ref.get()); // null
// 通过队列检测回收
Reference<?> r = queue.poll();
if (r != null) {
// 对象已被 GC,执行清理
cleanup();
}

3.5 引用队列(ReferenceQueue)#

ReferenceQueue 是引用被 GC 清除后的通知机制。软引用、弱引用、虚引用都可以关联一个队列。GC 清除引用后,引用对象本身会被入队,你可以从队列中取出它并执行善后操作。

sequenceDiagram participant App as 应用代码 participant GC as 垃圾回收器 participant Ref as 引用对象 participant Q as ReferenceQueue App->>Ref: 创建 SoftReference(obj, queue) Note over Ref: ref.get() → obj GC->>Ref: 检测到 obj 软可达/弱可达 GC->>Ref: 清除引用(referent = null) GC->>Q: 将 ref 入队 App->>Q: queue.poll() → ref App->>App: 执行清理逻辑

3.6 JVM 可达性层级#

JVM 定义了五级可达性,GC 据此决定对象的生死:

可达性级别含义GC 行为
强可达(Strongly Reachable)存在从 GC Roots 的强引用链不回收
软可达(Softly Reachable)非强可达,但存在软引用内存不足时回收
弱可达(Weakly Reachable)非软可达,但存在弱引用下次 GC 时回收
虚可达(Phantomly Reachable)非弱可达,但存在虚引用回收后入队通知
不可达(Unreachable)无任何引用可最终回收

四、变体与对比#

4.1 Java:完整的四级引用体系#

Java 拥有最完整的引用强度体系:Strong、Soft、Weak、Phantom 四级,配合 ReferenceQueue 实现回收通知。WeakHashMap 是弱引用的经典应用——键为弱引用,值随键的回收而自动清除。

4.2 Go:runtime.SetFinalizer#

Go 没有软引用和弱引用,最接近的是 runtime.SetFinalizer,它为对象注册一个在 GC 回收对象时调用的函数,功能上类似于虚引用的清理通知。

// 注册终结器:obj 被 GC 回收时调用 cleanup
runtime.SetFinalizer(obj, func(o *MyResource) {
o.Close() // 释放 native 资源
})

SetFinalizer 的局限:终结器执行顺序不确定,且对象可能在终结器中被意外复活。Go 社区普遍建议用 defer 和显式 Close() 替代,把 SetFinalizer 当安全网而非主要机制。

4.3 JavaScript / TypeScript:WeakRef + FinalizationRegistry#

ES2021 引入了 WeakRef(弱引用)和 FinalizationRegistry(类似虚引用的清理通知)。这是 JavaScript 第一次暴露引用强度控制给开发者。

const cache = new Map();
const registry = new FinalizationRegistry((key) => {
cache.delete(key); // 对象被回收后清理缓存条目
});
function getCached(key, loader) {
if (cache.has(key)) {
const ref = cache.get(key);
const value = ref.deref(); // 检查弱引用是否还有效
if (value !== undefined) return value;
}
const value = loader();
cache.set(key, new WeakRef(value));
registry.register(value, key); // 注册清理回调
return value;
}

4.4 Python:weakref 模块#

Python 提供 weakref.ref(弱引用)、weakref.WeakKeyDictionaryweakref.WeakValueDictionary。没有软引用和虚引用——Python 的引用计数 + GC 组合使得软引用的意义不大。

4.5 C#:WeakReference 与 ConditionalWeakTable#

C# 的 WeakReference 分短周期(Short)和长周期(Long)两种。更有趣的是 ConditionalWeakTable<TKey, TValue>——它相当于一个键为弱引用的字典,但值会随键的回收而自动释放,本质上是弱引用 + 终结器的组合。

4.6 WeakHashMap vs ConcurrentHashMap + WeakReference#

WeakHashMap 的键是弱引用,适合临时元数据关联。但它不是线程安全的。需要并发时,用 ConcurrentHashMap 配合 WeakReference 值,或者用 Guava 的缓存构建器指定弱引用策略。

方案线程安全键/值引用适用场景
WeakHashMap键弱引用单线程元数据关联
ConcurrentHashMap<K, WeakReference<V>>值弱引用并发缓存
Guava Cache + weakKeys/weakValues可配置生产级缓存

五、多语言实现#

5.1 Go:用 finalizer 模式实现缓存#

Go 没有软引用和弱引用,但可以用 runtime.SetFinalizer 配合手动缓存淘汰来模拟类似效果。

package weakcache
import (
"runtime"
"sync"
)
// Cache 基于 finalizer 的简易缓存
type Cache[K comparable, V any] struct {
mu sync.RWMutex
items map[K]*entry[V]
}
type entry[V any] struct {
value V
alive bool
}
// New 创建缓存
func New[K comparable, V any]() *Cache[K, V] {
return &Cache[K, V]{
items: make(map[K]*entry[V]),
}
}
// Set 添加缓存项,注册 finalizer 作为安全网
func (c *Cache[K, V]) Set(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
e := &entry[V]{value: value, alive: true}
c.items[key] = e
// finalizer 作为安全网:entry 不再被缓存引用时打印日志
// 注意:finalizer 不能替代显式清理,只是兜底
runtime.SetFinalizer(e, func(ee *entry[V]) {
if ee.alive {
// 如果还在 alive 状态被 GC 回收,说明缓存被意外清理
// 生产环境应记录监控指标
}
})
}
// Get 获取缓存项
func (c *Cache[K, V]) Get(key K) (V, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
e, ok := c.items[key]
if !ok || !e.alive {
var zero V
return zero, false
}
return e.value, true
}
// Remove 显式移除缓存项
func (c *Cache[K, V]) Remove(key K) {
c.mu.Lock()
defer c.mu.Unlock()
if e, ok := c.items[key]; ok {
e.alive = false
runtime.SetFinalizer(e, nil) // 取消 finalizer
delete(c.items, key)
}
}
Note

Go 的 runtime.SetFinalizer 有明显局限:执行时机不确定、不保证执行顺序、可能延迟对象的回收周期。上面的代码用 finalizer 做安全网,实际的缓存淘汰仍然依赖显式调用 Remove 或 LRU 策略。如果你的场景真正需要弱引用语义,考虑用 sync.Map + 定期清理来模拟。

5.2 TypeScript:WeakRef + FinalizationRegistry 实现对象缓存#

class ObjectCache<K, V extends object> {
private cache = new Map<K, WeakRef<V>>();
private registry: FinalizationRegistry<K>;
constructor() {
// 对象被 GC 回收时自动清理缓存条目
this.registry = new FinalizationRegistry((key: K) => {
const ref = this.cache.get(key);
// 二次检查:对象可能已被重新缓存
if (ref && ref.deref() === undefined) {
this.cache.delete(key);
}
});
}
// 获取缓存对象
get(key: K, loader: () => V): V {
const ref = this.cache.get(key);
if (ref) {
const value = ref.deref();
if (value !== undefined) return value;
// 弱引用已失效,清理残留条目
this.cache.delete(key);
}
// 加载并缓存
const value = loader();
this.cache.set(key, new WeakRef(value));
this.registry.register(value, key);
return value;
}
// 手动移除
delete(key: K): void {
this.cache.delete(key);
}
}

使用示例:

interface ImageResource {
width: number;
height: number;
data: Uint8Array;
}
const imageCache = new ObjectCache<string, ImageResource>();
// 首次加载
const img1 = imageCache.get("hero.png", () => loadImage("hero.png"));
// 后续获取直接返回缓存
const img2 = imageCache.get("hero.png", () => loadImage("hero.png"));
// img1 === img2,同一对象
// GC 回收后再次获取会重新加载
// (需要在没有其他强引用指向该 ImageResource 时)
Warning

FinalizationRegistry 的回调时机由 GC 决定,不能依赖它来做关键资源的确定性清理。对于文件句柄、网络连接等资源,仍然应该用 try/finally 或显式的 dispose() 模式。

六、生产验证#

项目源码位置用途
Guava Cacheguava/CaffeineCacheBuilder.weakKeys() / softValues() 允许用弱引用/软引用作为缓存策略。键用弱引用时,键对象被 GC 回收后条目自动失效
Python weakrefcpython/weakref.pyPython 标准库的弱引用实现。WeakKeyDictionary 用于给对象附加元数据而不阻止对象被回收
V8 WeakRefV8 WeakRef 实现V8 引擎对 ES2021 WeakRefFinalizationRegistry 的实现。弱引用的清除发生在 GC 的标记阶段

七、小结#

何时使用软引用:

  • 内存敏感的缓存——图片、解码数据、计算结果等可以重建的对象
  • 需要在内存不足时自动退让的场景,但充裕时尽量保留

何时使用弱引用:

  • 规范映射——WeakHashMapWeakKeyDictionary,给对象附加元数据但不阻止回收
  • 打破循环引用——Rust 的 Weak<T>、Python 的 weakref.ref
  • 监听器/回调列表——避免因注册回调而阻止对象被回收

何时使用虚引用:

  • 替代 finalize() 做资源清理——更安全,无法复活对象
  • 需要知道对象何时被 GC 回收,以便清理关联的外部资源

何时不用:

  • 需要确定性清理的关键资源——用 try/finallydefer、RAII 模式
  • 对缓存命中率有严格要求的场景——弱引用/软引用的回收时机不可控,命中率波动大
  • Go 项目中过度依赖 runtime.SetFinalizer——执行时机和顺序都不确定

八、参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

弱引用与引用强度(Weak / Soft / Phantom Reference)
https://blog.souloss.com/posts/programming/memory/memory-weak-references/
作者
Tsukimi
发布于
2026-06-13
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时