我了解不同类型的引用、可达性以及垃圾回收器的工作原理 - 我在一些个人项目中也使用了不同类型的引用。然而,有一个设计选择我不太理解:为什么
因为在评论中提出了这个问题:我提出这个问题并不是因为我有一个具体的问题,我认为这可能是解决方案。相反,我需要在我的硕士论文中描述各种“引用”类型,它们的相关性,并且我想正确地描述它们-包括设计选择背后的推理。
然而,我仍然可以想到一个明确不适用于使用“弱引用”的具体情况:
假设你正在编写一个为用户提供某种服务的库。让我们假设该库希望确保一次只存在一个此服务的实例-可以说它是一个“单例”-所以让我们将这个类称为“PublicSingletonService”。然而,让我们假设该服务需要许多资源才能存在-因此,一旦不再需要它,库就希望将其丢弃,并在需要时重新创建它。我认为这是一个合理的情况。
所以,该库对其单例持有一个WeakReference,并使用ReferenceQueue来检测单例是否不再需要并进行清理。当用户请求服务时,库会在WeakReference上调用get(),如果结果非null,则返回该结果,如果为null,则重新创建服务。
但是现在,使用该库的应用程序执行以下操作:
PhantomReference
的get
方法总是返回null
。
PhantomReference
主要(可能仅仅)用于对象“死亡”后的清理操作。所以,对于我的问题,通常的答案是“确保在清理开始后,对象不能再次变得(强)可达” - 或者,正如PhantomReference的文档所述:
但就我理解,“确保可回收对象保持不变”并不意味着为了确保可回收对象保持可回收状态,幻影引用的引用对象不可被检索:幻影引用的
get
方法始终返回null
。
get()
必须始终返回null
:只要幻影引用没有被清除,它的引用对象就不可回收,也不会被加入队列,因此清理操作不会启动 - 这意味着get()
可以“安全地”访问引用对象。只有在引用被清除后,它才变得可回收,所以get()
从那时起无论如何都会返回null
。也不存在竞争条件的可能性(例如:一个对象有两个幻影引用;一个被清除并加入队列,然后通过另一个的get
方法重新访问对象),因为文档指出所有清除操作都是原子性的(我强调):
在那个时候,它将原子性地清除该对象的所有幻影引用以及该对象可达的其他幻影可达对象的所有幻影引用。
因为在评论中提出了这个问题:我提出这个问题并不是因为我有一个具体的问题,我认为这可能是解决方案。相反,我需要在我的硕士论文中描述各种“引用”类型,它们的相关性,并且我想正确地描述它们-包括设计选择背后的推理。
然而,我仍然可以想到一个明确不适用于使用“弱引用”的具体情况:
假设你正在编写一个为用户提供某种服务的库。让我们假设该库希望确保一次只存在一个此服务的实例-可以说它是一个“单例”-所以让我们将这个类称为“PublicSingletonService”。然而,让我们假设该服务需要许多资源才能存在-因此,一旦不再需要它,库就希望将其丢弃,并在需要时重新创建它。我认为这是一个合理的情况。
所以,该库对其单例持有一个WeakReference,并使用ReferenceQueue来检测单例是否不再需要并进行清理。当用户请求服务时,库会在WeakReference上调用get(),如果结果非null,则返回该结果,如果为null,则重新创建服务。
但是现在,使用该库的应用程序执行以下操作:
- 请求服务单例
- 创建一个对象(称为“持有者”),该对象引用服务单例,并具有一个finalizer,该finalizer将使对象(以及服务单例)再次可达。
- 清除所有对服务单例和持有者的剩余引用,使其仅通过库的弱引用弱可达
- 使用大量内存(或调用System.gc(),以便GC清除弱引用并启动持有者的finalization
- 以下三件事以任意顺序发生:
- 持有者的finalizer "复活"了服务的单例
- 库在单例之后进行清理
- 应用程序请求另一个服务的单例 - 这将是新创建的。 现在,尽管库没有任何问题的finalizer,应用程序有两个单例实例!
库可以通过保留PhantomReference
来部分防止这种情况,除了WeakReference
之外,并且只有在PhantomReference
被清除时才进行清理或创建第二个实例。然而,这有一个问题:如果WeakReference
被清除,但是PhantomReference
没有被清除,并且应用程序要求库提供服务的实例,库既无法访问旧实例也无法创建新实例。
如果PhantomReference.get()
不总是返回null
,这个问题就可以解决:只需让库使用PhantomReference
而不是WeakReference
,并使用PhantomReference.get()
。
这里是一个演示这个问题的示例程序:
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
{
// Application code initializes the library
Library library = new Library();
// "Library" creates the singleton of the public service and tries to detect once it is _definitely_ unreachable.
PublicSingletonService singleton = library.getSingleton();
// The application code then does weird stuff with the singleton (maliciously, bad programming, or convoluted interactions):
// make holder
ApplicationHolder holder = new ApplicationHolder(singleton);
// clear the last remaining strong ref to the singleton
singleton = null;
// make sure the libraries' weakref is actually cleared - simulates the application using much memory.
System.gc();
// The "library" tries to detect whether the object was cleared, and clean up if it is.
// In this example, we made sure that it is.
library.doCleanupIfNecessary();
// The application asks the library for the singleton again - because it has been cleared, the library recreates the singleton.
PublicSingletonService secondSingleton = library.getSingleton();
// Now, the application magically resurrects the first singleton
PublicSingletonService resurrectedSingleton = holder.getReferent();
if(resurrectedSingleton == null)
throw new IllegalStateException("example failed");
System.out.println("Resurrection successful! resurrected: " + resurrectedSingleton);
// At this point, the weak references to the object have been cleared,
// but the object is still strongly reachable -
// and that without the libraries' class having any finalizer.
System.out.println("Application now has two singletons: " + resurrectedSingleton + ", " + secondSingleton);
}
private static class ApplicationHolder
{
private final Semaphore referrerFinalizationComplete;
private FinalizableReferrer referrerAfterFinalization;
public ApplicationHolder(PublicSingletonService referent)
{
referrerFinalizationComplete = new Semaphore(0);
// explicitly don't keep a reference to this, to let it be able to be reclaimed
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("Finalizer making referent reachable again");
referrer.referrerAfterFinalization = this;
referrer.referrerFinalizationComplete.release();
}
public PublicSingletonService getReferent()
{
return referent;
}
}
}
}
public static class Library
{
// just for demonstration; in reality, this would have to be thread-safe and whatnot
private int instanceCounter = 0;
private final ReferenceQueue<PublicSingletonService> refqueue;
private WeakReference<PublicSingletonService> publicSingletonObjectRef;
public Library()
{
this.refqueue = new ReferenceQueue<>();
}
public void doCleanupIfNecessary() throws InterruptedException
{
// In practice, this would be poll(), not remove() - however, in this example,
// even though we called System.gc() the enqueueing doesn't happen fast enough.
Reference<? extends PublicSingletonService> poll = refqueue.remove();
if(poll != null)
{
System.out.println("Reference to referent was cleared; weakRef.get() is " + publicSingletonObjectRef.get()); // is null
// The library cleans up what it needs to clean up
System.out.println("Library \"cleans up\"");
} else
// In practice, the library would do nothing here.
throw new IllegalStateException("example failed");
}
public PublicSingletonService getSingleton()
{
if(publicSingletonObjectRef != null)
{
// This is the call to get() which would not be possible with a phantom reference
PublicSingletonService previousInstance = publicSingletonObjectRef.get();
if(previousInstance != null)
return previousInstance;
}
PublicSingletonService publicSingletonObjectInstance = new PublicSingletonService("Instance #" + 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("Creating singleton object: " + value);
}
@Override
public String toString()
{
return value;
}
}
}
}
finalize
方法将在未来的 Java 版本中被移除。 - undefined