我一直在阅读有关PhantomReference https://www.baeldung.com/java-phantom-reference的文章以及在那里找到的简化示例代码:
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
Object object = new Object();
PhantomReference<Object> phantomReference = new PhantomReference<>(object, referenceQueue);
object = null;
System.gc();
Thread.sleep(1_000);
System.out.println("isEnqueued() after GC: " + phantomReference.isEnqueued());
Reference reference = referenceQueue.poll();
if(reference != null) {
System.out.println("isEnqueued() after poll(): " + phantomReference.isEnqueued());
}
}
此处输出:
isEnqueued() after GC: true
isEnqueued() after poll(): false
因此,一切工作都按预期进行,将对对象的强引用设置为null(GC会将其检测到)并将幻像引用添加到队列中。
现在在那篇文章中,他们说:“垃圾收集器在执行其引用的finalize方法之后,将一个幻像引用添加到引用队列。这意味着该实例仍在内存中。”
所以我想做一个测试并覆盖finalize方法,例如:
Object object = new Object() {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize()");
}
};
但是输出有所不同,幻像引用不再添加到队列中:
finalize()
isEnqueued() after GC: false
有人可以解释为什么在此更改输出之后不同,以及如何更改此代码,以便将幻像引用添加到队列中?
我已经在JDK 8和11上对此进行了测试,两个平台上的结果相同。
答案 0 :(得分:4)
“垃圾回收器在执行其引用的finalize方法之后,将一个幻像引用添加到引用队列。”充其量有点草率。
您应该参考the specification:
如果垃圾收集器在某个时间点确定幻像引用的引用对象为phantom reachable,那么在那时或以后的某个时间,它将使引用排队。
“幻影可达”状态的链接定义:
对象幻影可到达,如果它既不是强,软,也不是弱可达,则已经完成,并且有一些幻影引用对其进行引用。
因此,如果仅通过幻象引用进行引用,则“在执行其引用的finalize方法之后”对象是幻象可到达的,因此该对象将在该之后而不是立即入队。由于对象在执行其finalize()
方法期间是高度可访问的,因此它至少需要一个额外的垃圾回收周期来检测它变为幻像可访问。然后,“在那个时候或以后的某个时间”它将被排队。
如果将程序更改为
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
Object object = new Object() {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize()");
}
};
PhantomReference<Object> phantomReference = new PhantomReference<>(object, referenceQueue);
object = null;
System.gc();
Thread.sleep(1_000);
System.gc();
Thread.sleep(1_000);
System.out.println("isEnqueued() after GC: " + phantomReference.isEnqueued());
Reference reference = referenceQueue.poll();
if(reference != null) {
System.out.println("isEnqueued() after poll(): " + phantomReference.isEnqueued());
}
您很可能会看到所需的输出,但是必须强调的是,不能保证在调用System.gc()
时垃圾收集器会真正运行,或者不能保证垃圾收集器在特定的时间内完成运行。运行或它将发现特定周期内的所有无法访问的对象。此外,入队是在gc周期之后异步发生的,因此,即使到垃圾收集器完成并检测到特殊的可达性状态时,也可能要经过一段时间才可以入队。
请注意,“这意味着实例仍在内存中。”这句话也没有使它正确,但是在这种情况下,它是基于对Java核心开发人员的误解。 >
创建API时,在规范中添加了一个句子,即使在Java 8版本中也可以找到:
与软引用和弱引用不同,幻象引用在排队时不会被垃圾收集器自动清除。通过幻像引用可访问的对象将保留,直到清除所有此类引用或它们自身无法访问为止。
这可能导致天真的假设,即该对象仍必须在内存中,但是The Java® Language Specification指出:
可以设计程序的优化转换,以将可到达的对象数量减少到少于天真的被认为可到达的对象数量。
简单地说,如果程序的行为不变,则对象的内存可能会更早地回收。这尤其适用于应用程序根本无法使用对象(如幻像引用)的情况。如果该对象不再位于内存中,程序的行为就不会改变,因此您不能假定它实际上是。
这引发了一个问题,为什么规范中根本没有添加不删除幻影引用的规则。正如this answer中讨论的那样,该问题已提出,根本无法回答。因此,该规则已在Java 9中删除,并且像弱引用和软引用一样在排队时清除了幻像引用。这是一个更强的理由,不要假设该对象仍在内存中,因为现在即使非优化环境也可以在此时回收该对象的内存。