首页 / 资讯中心 / 文章详情

3.0.2 Java 的引用

3.0.2 Java 的引用 ★ FEATURED ARTICLE
Java 的引用在 JVM 中引用reference是一种数据类型用于定位某个对象或者 null。垃圾收集器正是沿着这种引用关系来推导对象是否存活、是否应当被回收。在传统认知里对象的状态是非黑即白的——要么从 GC Roots 出发沿强引用链可达存活要么不可达被判定为垃圾并回收。大部分场景下这个设计没有问题但存在一些特殊需求弹性缓存设计某些计算成本高昂但不致命的缓存数据希望在内存充足时保留以提升性能在内存吃紧时自动回收从而避免 OOM。生命周期解耦在事件监听器或回调机制中如果注册中心持有目标对象的强引用会导致目标对象在业务生命周期结束后依然无法被回收从而引发内存泄漏。特殊资源的清理某些 GC 无法直接管理的资源如堆外内存需要在对应的 Java 对象被销毁时触发安全的清理逻辑。为了赋予开发者更灵活的内存控制能力Java 从 JDK 1.2 开始引入了软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。它们允许开发者在一定程度上介入对象的生命周期管理。虽然本文主要就是在介绍这三种引用除非你完全清楚其背后的运行逻辑并且拥有十分明确的不可避免的使用理由否则不要使用这些引用类及其浅层封装强引用强引用就是最传统的引用关系即代码中随处可见的Object o new Object()这种引用赋值。从 GC Roots 出发沿强引用链可达就会视为存活对象不会被垃圾收集器回收。软引用SoftReference仅软引用可达的对象在内存充足时不会被垃圾收集器回收在发生内存压力时GC 会根据策略回收它们。JVM 保证在抛出堆空间不足的 OOM 之前会清空所有指向“仅软引用可达”对象的软引用换而言之只有在清除软引用仍无法腾出足够内存时才会抛出 OOM 异常软引用试图把缓存驱逐策略内嵌进引用模型从设计动机上看有一定合理性但在实践中基本是失败的JVM 并没有足够的上下文来判断哪个对象“更值得保留”。应用没有应用不要使用软引用问题历史上软引用曾被用作缓存哪怕是现在 SoftReference 的 java doc 中也推荐其应用场景为缓存但其存在严重的缓存雪崩问题。软引用被回收的时机不可预测高并发下大量 key 同时失效重建开销集中爆发而程序员对此几乎毫无控制能力。有缓存需求请使用 Caffeine 等缓存组件此外软引用需要额外处理会对 GC 带来额外处理开销有可能造成更长的停顿时间弱引用WeakReference仅弱引用可达的对象会被垃圾收集器直接回收与软引用不同弱引用不试图干涉 GC 的回收决策。弱引用的本质是解耦对象的所有权与生命周期是一个合理的语义补充应用监听器/回调防内存泄漏这是最典型的用法。如果用强引用持有监听器注册方忘记反注册就会造成泄漏。用弱引用持有监听器宿主对象生命周期结束后自然被回收框架侧不需要依赖调用方的纪律性附加元数据WeakHashMap的核心场景key 是某个外部对象想附加一些元数据但不想因为这个 Map 的存在就延长 key 对象的生命周期。规范化映射用于确保内存中逻辑相等的对象只有一份物理实例。如果用强引用做享元池池子会不断膨胀导致内存泄漏利用弱引用持有池中的规范实例可以在外部业务不再使用该实例时允许 GC 将其自动回收问题弱引用的逻辑本身没有问题但所有弱引用操作本质上都是业务线程与 GC 线程之间的竞态使用时需要格外注意尤其注意WeakHashMap的使用它存在很多隐含且难以调试的竞态问题虚引用PhantomReference一个对象是否有虚引用存在完全不会对其生命周期构成影响甚至无法通过虚引用取得对象实例。为对象设置虚引用关联的唯一目的是在该对象被回收时收到一个通知虚引用本质上是 Java 缺乏可靠析构函数的补偿机制。这个工作本来应该由finalize()完成但finalize存在一系列严重缺陷对象复活、GC 需要单独处理一轮导致的性能问题、只执行一次、执行线程不可控、执行时机不确定、Finalizer 队列积压等。虚引用通过一个关键设计规避了其中最棘手的问题它不暴露原对象的引用业务清理逻辑无法访问原对象对象复活的路被彻底断掉了。此外它的执行线程也是可控的由开发者手动处理ReferenceQueue来驱动java.lang.ref.Cleaner就是虚引用的浅层封装会自己管理一个守护线程应用在业务代码中没有应用不要在业务代码中使用虚引用问题持有外部资源的对象在销毁时释放资源这个需求确实存在。但由于可达性分析的固有局限析构的执行时机永远无法保证实时性。资源应当使用try-with-resources手动关闭而不是依赖 GC 清理甚至不应该用虚引用来兜底它本质上是在掩盖问题而不是解决问题尽管 JDK 和很多开源框架/库中大量使用 Cleaner 进行兜底但是这是建立在面对的是不可信的调用方的基础上的。如果你写的不是 JDK 或框架/库不要使用虚引用DirectByteBuffer 除外它设计有问题导致只能用 Cleaner 关闭
阅读完成 · 觉得有帮助?
咨询建站