Java PhantomReference 与 finalize()

Java PhantomReference vs finalize()

我一直在阅读这篇关于 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 检测到该引用并将幻象引用添加到队列中。

在那篇文章中他们说:"The Garbage Collector adds a phantom reference to a reference queue after the finalize method of its referent is executed. It implies that the instance is still in the memory."

所以我想进行测试并重写 finalize 方法,例如:

Object object = new Object() {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize()");
    }
};

但随后输出不同,幻象引用不再添加到队列中:

finalize()
isEnqueued() after GC: false

有人可以解释为什么在此更改后输出不同以及如何更改此代码以便将幻像引用添加到队列中吗?

我一直在 JDK 8 和 11 上进行测试,两个平台上的结果相同。

语句“垃圾收集器在执行其引用对象的终结方法后向引用队列添加一个幻象引用。”充其量有点草率。

你应该参考the specification:

If the garbage collector determines at a certain point in time that the referent of a phantom reference is phantom reachable, then at that time or at some later time it will enqueue the reference.

而“幻影可达”的链接定义指出:

An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.

因此,如果对象仅被幻影引用引用,则“在执行其引用对象的最终化方法之后”是幻影可达的,因此将在之后入队,但不会立即。由于对象在其 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() 时实际上会 运行 或者它会在当它 运行s 或它将在特定周期内找到所有无法访问的对象时的特定时间。此外,入队在 gc 周期之后异步发生,因此即使在垃圾收集器完成并检测到特殊的可达性状态时,在引用入队之前可能还需要额外的时间。


请注意“这意味着该实例仍在内存中”这句话。也没有做对,但在这种情况下,它是基于甚至在 Java 核心开发人员方面的误解。

创建 API 时,在规范中添加了一句话,即使在 Java 8 版本中也可以找到:

Unlike soft and weak references, phantom references are not automatically cleared by the garbage collector as they are enqueued. An object that is reachable via phantom references will remain so until all such references are cleared or themselves become unreachable.

这可能会导致对象仍然必须在内存中的天真假设,但 The Java® Language Specification 指出:

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable.

简单地说,如果程序的行为没有改变,对象的内存可能会提前回收。这尤其适用于应用程序根本无法使用该对象的场景,例如幻影引用。如果对象不再在内存中,程序的行为不会改变,所以你不能假设它实际上是。

这就引出了一个问题,为什么要将不清除幻象引用的规则添加到规范中。正如 中讨论的那样,这个问题被提出来了,根本无法回答。因此,此规则已在 Java 9 中删除,幻象引用在入队时被清除,如弱引用和软引用。这是不假设对象仍在内存中的更有力的理由,因为现在即使是非优化环境也可以在此时回收对象的内存。