为何PhantomReference.get()始终返回null?设计逻辑探究
我熟悉Java中的各类引用、可达性概念及垃圾回收机制,也在个人项目中使用过不同类型的Reference,但有一个设计选择令我困惑:为何PhantomReference的get()方法始终返回null?
PhantomReference主要用于对象“消亡”后的清理操作,常见解释是“确保可回收对象保持可回收状态,避免清理启动后对象重新变为强可达”,正如官方文档所述:
为确保可回收对象保持该状态,无法获取虚引用的引用对象:虚引用的get方法始终返回null。
但我认为,“确保可回收对象保持可回收”并不意味着get()必须始终返回null:只要PhantomReference未被清除,其引用对象就不可回收,也不会入队,清理操作尚未启动——此时get()可以安全返回引用对象。只有当引用被清除后,对象才会变为可回收,此时get()自然返回null。且不存在竞态条件,因为文档说明所有清除操作都是原子性的:
此时会原子性地清除指向该对象的所有虚引用,以及指向该对象可到达的其他虚可达对象的所有虚引用。
我提出该问题并非为解决具体业务问题,而是为在硕士论文中准确描述各类Reference类型的设计逻辑。不过我想到一个WeakReference无法覆盖的场景:
假设编写一个提供单例服务(PublicSingletonService)的库,该服务占用大量资源,需在无人使用时回收,再次请求时重建。库通过WeakReference持有单例,用ReferenceQueue检测回收并清理。但应用可通过带终结器的holder对象复活单例,导致库创建新实例,最终出现两个单例。
若同时使用PhantomReference和WeakReference,会出现WeakReference已清除但PhantomReference未清除的情况,此时库既无法访问旧实例也无法创建新实例。若PhantomReference.get()不始终返回null,直接用PhantomReference替代WeakReference即可解决该问题。以下是示例代码:
package weirdjava; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.concurrent.Semaphore; import weirdjava.StronglyReachableAfterWeakReferenceCleared.Library.PublicSingletonService; public class StronglyReachableAfterWeakReferenceCleared { public static void main(String[] args) throws InterruptedException { Application.run(); } public static class Application { public static void run() throws InterruptedException { // 应用代码初始化库 Library library = new Library(); // 库创建公共服务的单例,并尝试检测它何时完全不可达 PublicSingletonService singleton = library.getSingleton(); // 应用代码对单例执行一些非常规操作(恶意行为、糟糕编程或复杂交互): // 创建holder ApplicationHolder holder = new ApplicationHolder(singleton); // 清除指向单例的最后一个强引用 singleton = null; // 确保库的弱引用被实际清除 - 模拟应用占用大量内存的场景 System.gc(); // 库尝试检测对象是否已被清除,并在必要时执行清理 // 在这个示例中,我们确保对象已被清除 library.doCleanupIfNecessary(); // 应用再次向库请求单例 - 因为旧实例已被清除,库会创建新实例 PublicSingletonService secondSingleton = library.getSingleton(); // 现在,应用神奇地复活了第一个单例 PublicSingletonService resurrectedSingleton = holder.getReferent(); if(resurrectedSingleton == null) throw new IllegalStateException("示例执行失败"); System.out.println("复活成功!复活的实例:" + resurrectedSingleton); // 此时,指向对象的弱引用已被清除, // 但对象仍处于强可达状态 - // 且库的类并没有任何终结器 System.out.println("应用现在拥有两个单例:" + resurrectedSingleton + ", " + secondSingleton); } private static class ApplicationHolder { private final Semaphore referrerFinalizationComplete; private FinalizableReferrer referrerAfterFinalization; public ApplicationHolder(PublicSingletonService referent) { referrerFinalizationComplete = new Semaphore(0); // 显式不保留对该对象的引用,使其能够被回收 new FinalizableReferrer(this, referent); } public PublicSingletonService getReferent() throws InterruptedException { referrerFinalizationComplete.acquire(); return referrerAfterFinalization.getReferent(); } private static class FinalizableReferrer { private final ApplicationHolder referrer; private final PublicSingletonService referent; public FinalizableReferrer(ApplicationHolder referrer, PublicSingletonService referent) { this.referrer = referrer; this.referent = referent; } @Override protected void finalize() { System.out.println("终结器使引用对象再次可达"); referrer.referrerAfterFinalization = this; referrer.referrerFinalizationComplete.release(); } public PublicSingletonService getReferent() { return referent; } } } } public static class Library { // 仅用于演示;实际场景中需要保证线程安全等 private int instanceCounter = 0; private final ReferenceQueue<PublicSingletonService> refqueue; private WeakReference<PublicSingletonService> publicSingletonObjectRef; public Library() { this.refqueue = new ReferenceQueue<>(); } public void doCleanupIfNecessary() throws InterruptedException { // 实际中应该用poll()而非remove() - 但在这个示例中, // 即使调用了System.gc(),入队操作也不够快 Reference<? extends PublicSingletonService> poll = refqueue.remove(); if(poll != null) { System.out.println("引用对象已被清除;weakRef.get()返回:" + publicSingletonObjectRef.get()); // 返回null // 库执行必要的清理操作 System.out.println("库执行清理"); } else // 实际中库会什么都不做 throw new IllegalStateException("示例执行失败"); } public PublicSingletonService getSingleton() { if(publicSingletonObjectRef != null) { // 这个get()调用无法用PhantomReference实现 PublicSingletonService previousInstance = publicSingletonObjectRef.get(); if(previousInstance != null) return previousInstance; } PublicSingletonService publicSingletonObjectInstance = new PublicSingletonService("实例 #" + instanceCounter ++); publicSingletonObjectRef = new WeakReference<>(publicSingletonObjectInstance, refqueue); return publicSingletonObjectInstance; } public static class PublicSingletonService { private final String value; public PublicSingletonService(String value) { this.value = value; System.out.println("创建单例对象:" + value); } @Override public String toString() { return value; } } } }
内容的提问来源于stack exchange,提问作者Haspamelodica

