Skip to content

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 / AndroidDart / Flutter工程边界
阻止对象被回收强引用普通对象引用只要引用链仍可达,GC 就不能回收;不代表页面一定合理存活
不阻止对象存活WeakReferenceWeakReference<T>两者都需要判空,语义和运行时细节不能完全混用
回收后通知ReferenceQueue + PhantomReference / CleanerFinalizer都不是确定时间的资源释放 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
  • 可能执行顺序
    1. GC 前 get(): [B@...
    2. GC 前 queue.poll(): null
    3. GC 后 get(): null(可能出现)
    4. GC 后 queue.poll(): java.lang.ref.WeakReference@...(可能出现)

Android / Flutter / Web / Backend 对照 ​

维度Java / AndroidDart / FlutterJavaScript (Node/Web)
弱引用实现WeakReference<T>WeakReference<T> (Dart 2.17+)WeakRef / WeakMap / WeakSet
终结者/清理器Cleaner / PhantomReferenceFinalizer (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 监听测试

复习检查题 ​

  1. 为什么在 Android 开发中,官方推荐使用 LruCache 替代基于 SoftReference 的缓存?

    答:软引用的清除时机由 GC 和运行时策略决定,应用不能把它当作稳定的容量控制协议;即使暂时没有 OOM,也不能据此保证对象长期保留。LruCache 采用强引用持有条目,并基于显式的容量、大小和淘汰规则控制上限,命中率和释放行为更可预测。

  2. 虚引用(PhantomReference)的 .get() 方法返回值是什么?它主要用来做什么?

    答:虚引用的 .get() 方法永远返回 null。虚引用无法用来获取原对象,它的唯一作用是必须配合 ReferenceQueue 使用;当原对象被 GC 回收时,虚引用会被放入队列,从而允许系统在 Native 层或框架层安全地收到“对象已被 GC”的通知,用作资源清理(如 Cleaner/DirectByteBuffer 堆外内存释放)。

速记 ​

  • 强弱软虚:强引用保持可达对象,软引用由 GC 根据压力和策略处理,弱引用不阻止回收,虚引用主要用于回收通知。
  • 弱引用判空:使用 weakRef.get() 必须先赋给局部强引用并做 null 检查。
  • 缓存禁软引用:软引用的清除时机不可预测,现代缓存统一使用 LruCache 等显式容量和淘汰策略。

站点构建时间:2026/8/24 23:43:17