Appearance
Java / Android 引用类型与 Dart 弱引用对照
回到总览:内存、生命周期、系统约束
相关模块:JVM / ART / Dart / Flutter 内存模型总览 · JVM / ART 垃圾回收算法与 GC 演进深度剖析 · Android / Java 内存泄漏定位、LeakCanary 原理与 Profiler 实战
一句话定位
Java 的四种引用类型描述了“GC 如何看待某条引用”:强引用通常保持对象存活,软引用由 GC 根据内存压力决定是否清除,弱引用不阻止对象在 GC 中被回收,虚引用主要用于回收通知。工程重点是理解可达性、引用队列、缓存淘汰和生命周期治理;软引用不应被当成可预测的缓存容量控制方案。
Dart/Flutter 不能直接套用 Java 四种引用模型:Dart 有 WeakReference 和 Finalizer,但没有 Java 意义上可替代 SoftReference 的标准引用类型;Flutter 的图片和业务缓存通常依靠显式 LRU/容量/生命周期策略管理。
代码索引
计划补齐的实验:
labs/android/host/topics/memory-lifecycle/reference-types-demo/— 四种引用类型 GC 回收时机验证与 ReferenceQueue 监听测试
为什么需要
- 软引用和弱引用差在哪?分别适合什么?
- 一句话答:软引用由 GC 根据内存压力和实现策略决定是否清除,不适合当作稳定缓存策略;弱引用不应单独保持对象存活,适合辅助观察或解耦引用,但真正的生命周期问题仍应靠取消订阅、移除回调和显式淘汰解决。(详见下文 §1及复习题 1)
WeakHashMap为什么适合做缓存 / 监听注册表?- 一句话答:
WeakHashMap的 Entry Key 使用了弱引用;当 Key 没有外部强引用并被 GC 清理后,条目会在后续处理时被移除,可以降低注册表忘记清理的风险。但如果 value 反向持有 key,或业务本身需要明确的注销时机,仍然不能只依赖WeakHashMap。(详见下文 §1、§2)
- 一句话答:
- Handler 里为什么推荐弱引用持 Activity?
- 一句话答:Handler 消息延迟期间若强持 Activity,页面销毁后 Activity 仍可达导致内存泄漏;弱引用 + 判空可切断强引用链,但最根本的解法还是“延迟任务要在 onDestroy 时随生命周期取消”。(详见下文 §2.2)
- 虚引用用来干什么?
- 一句话答:不能通过虚引用访问对象(
.get()永远返回 null),仅在对象被 GC 回收时通过关联的ReferenceQueue收到通知,用于“对象回收后的底层资源释放”(如 DirectBuffer 的堆外内存清理与 Cleaner 机制)。(详见下文 §1及复习题 2)
- 一句话答:不能通过虚引用访问对象(
底层机制
1. 四大引用类型回收时机与生命周期
图注补充:只要 GC Roots 通过强引用可达,通常不会回收;软引用由 GC 根据内存压力和实现策略清除;弱引用不阻止对象在 GC 中被清除;虚引用不能通过
get()访问对象,只配合ReferenceQueue做回收通知。
四种引用属性对比表
| 引用类型 | 回收时机 | 配合 ReferenceQueue 作用 | 常见应用场景 |
|---|---|---|---|
| 强引用 (Strong) | 只要 GC Roots 可达,绝不回收(宁可 OOM) | 否 | 默认所有赋值操作(如 Object obj = new Object()) |
| 软引用 (Soft) | 由 GC 根据内存压力和实现策略决定 | 可选 | 不建议作为现代缓存的容量控制 |
| 弱引用 (Weak) | 不阻止对象在 GC 中被清除 | 是 (LeakCanary 监听) | Handler/Listener 辅助解耦、WeakHashMap |
| 虚引用 (Phantom) | 不保持对象存活;对象进入相应清理阶段后可通过队列获知 | 必须配合 | Cleaner / Native 堆内存清理通知 |
Java 与 Dart 的对应关系
| 能力 | Java / Android | Dart / Flutter | 工程边界 |
|---|---|---|---|
| 阻止对象被回收 | 强引用 | 普通对象引用 | 只要引用链仍可达,GC 就不能回收;不代表页面一定合理存活 |
| 不阻止对象存活 | WeakReference | WeakReference<T> | 两者都需要判空,语义和运行时细节不能完全混用 |
| 回收后通知 | ReferenceQueue + PhantomReference / Cleaner | Finalizer | 都不是确定时间的资源释放 API;文件、Socket、Native 指针仍应显式关闭 |
| 内存敏感缓存 | 旧式 SoftReference | 没有 Java SoftReference 对应物 | Flutter/Android 都更适合显式 LRU、容量和失效策略 |
Dart WeakReference / Finalizer 的最小心智模型
dart
final object = SomeResource();
final weak = WeakReference(object);
final target = weak.target;
if (target != null) {
// 先拿到局部强引用,再在当前同步片段内使用
target.doSomething();
}WeakReference 的作用是“不因为这条辅助引用而延长对象寿命”,不是让调用方可以放心持有一个永远有效的对象。Finalizer 可以在对象不可达后通知清理外部资源,但回调时间不确定、进程终止时也不保证执行,所以 FFI、文件、订阅等资源仍应在明确生命周期中主动释放。
2. 弱引用 + 引用队列 (ReferenceQueue) 工作机制
当 GC 判定一个仅被 WeakReference 或 PhantomReference 持有的对象准备回收时:
图注补充:Queue: 自动将 Ref 句柄压入
ReferenceQueue;App: 取出 Ref 句柄,获知对象已进入引用清理通知阶段。它不等于所有 Native 或外部资源都已经释放。
代码示例与回收验证
java
// 弱引用结合 ReferenceQueue 监听测试
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
WeakReference<byte[]> weakRef = new WeakReference<>(new byte[1024 * 1024], queue);
System.out.println("GC 前 get(): " + weakRef.get()); // 输出: [B@...
System.out.println("GC 前 queue.poll(): " + queue.poll()); // 输出: null
// 强制触发一次 GC
System.gc();
Thread.sleep(100);
System.out.println("GC 后 get(): " + weakRef.get()); // 可能为 null;GC 不保证按这里的时间完成
System.out.println("GC 后 queue.poll(): " + queue.poll()); // 可能取到 WeakReference,也可能仍为 null- 可能执行顺序
GC 前 get(): [B@...GC 前 queue.poll(): nullGC 后 get(): null(可能出现)GC 后 queue.poll(): java.lang.ref.WeakReference@...(可能出现)
Android / Flutter / Web / Backend 对照
| 维度 | Java / Android | Dart / Flutter | JavaScript (Node/Web) |
|---|---|---|---|
| 弱引用实现 | WeakReference<T> | WeakReference<T> (Dart 2.17+) | WeakRef / WeakMap / WeakSet |
| 终结者/清理器 | Cleaner / PhantomReference | Finalizer (Dart 2.17+) | FinalizationRegistry |
| 缓存首选 | LruCache (强引用 + 显式淘汰) | ImageCache (LRU) | Map + LRU 淘汰 |
常见误配、事故后果与排障
1. 事故:误将 WeakReference.get() 的结果不判空直接使用
- 误配原因:从弱引用获取对象后直接调用方法:
weakRef.get().doSomething()。 - 后果:如果在调用的一瞬间正好触发了 GC,
get()会返回null,直接抛出NullPointerException导致崩溃! - 排障与修法:必须先用局部强引用变量保存
get()结果,并显式判空:
kotlin
val target = weakRef.get()
if (target != null) {
target.doSomething() // 安全使用局部强引用
}与相近概念对比
| 概念 | 能否通过 .get() 拿到原对象 | 核心价值 |
|---|---|---|
| 弱引用 (WeakReference) | 能(若尚未被 GC 回收) | 解耦长短生命周期,防内存泄漏 |
| 虚引用 (PhantomReference) | 永远返回 null | 仅用于跟踪对象被 GC 回收的事件(取代危险的 finalize()) |
对应实验
计划补齐的实验:
labs/android/host/topics/memory-lifecycle/reference-types-demo/— 四种引用类型 GC 回收时机验证与 ReferenceQueue 监听测试
复习检查题
为什么在 Android 开发中,官方推荐使用
LruCache替代基于SoftReference的缓存?答:软引用的清除时机由 GC 和运行时策略决定,应用不能把它当作稳定的容量控制协议;即使暂时没有 OOM,也不能据此保证对象长期保留。
LruCache采用强引用持有条目,并基于显式的容量、大小和淘汰规则控制上限,命中率和释放行为更可预测。虚引用(PhantomReference)的
.get()方法返回值是什么?它主要用来做什么?答:虚引用的
.get()方法永远返回null。虚引用无法用来获取原对象,它的唯一作用是必须配合ReferenceQueue使用;当原对象被 GC 回收时,虚引用会被放入队列,从而允许系统在 Native 层或框架层安全地收到“对象已被 GC”的通知,用作资源清理(如 Cleaner/DirectByteBuffer 堆外内存释放)。
速记
- 强弱软虚:强引用保持可达对象,软引用由 GC 根据压力和策略处理,弱引用不阻止回收,虚引用主要用于回收通知。
- 弱引用判空:使用
weakRef.get()必须先赋给局部强引用并做 null 检查。 - 缓存禁软引用:软引用的清除时机不可预测,现代缓存统一使用
LruCache等显式容量和淘汰策略。