Java PhantomReference与finalize()

时间:2018-12-17 19:54:00

标签: java reference garbage-collection phantom-reference

我一直在阅读有关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上对此进行了测试,两个平台上的结果相同。

1 个答案:

答案 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中删除,并且像弱引用和软引用一样在排队时清除了幻像引用。这是一个更强的理由,不要假设该对象仍在内存中,因为现在即使非优化环境也可以在此时回收该对象的内存。