对 `this` 使用 PhantomReference 是否安全?

Is it safe to have a PhantomReference to `this`?

我有一个共享资源,我想知道还有多少其他对象仍在使用该资源。为此,我想使用 PhantomReferences.

因为 ReferenceQueues 不跟踪为它们注册的引用(source,部分 "Notification"),我的想法是将引用存储为被跟踪对象的字段:

class Foo {
    private PhantomReference<Foo> thisReference;

    public Foo(ReferenceQueue<Foo> queue) {
        thisReference = new PhantomReference<>(this, queue);
    }
}

基于 PhantomReferences 的 Java 9(+) 行为,这是否安全,或者是否有可能在没有将引用添加到队列的情况下对实例进行垃圾回收?

文档说:

Suppose the garbage collector determines at a certain point in time that an object is phantom reachable. At that time it will atomically clear all phantom references to that object and all phantom references to any other phantom-reachable objects from which that object is reachable. At the same time or at some later time it will enqueue those newly-cleared phantom references that are registered with reference queues.

但是它没有提到是否可以在引用入队之前进行垃圾回收。

我认为你所做的不会奏效。

对象幻影可达的先决条件是:

  • 对象不是强可达、软可达或弱可达,
  • 对象已经确定,
  • 对象可以从 GC 根通过至少一个带有幻象引用的路径到达。

在您的情况下,满足前两个先决条件,但不满足第三个。如果我们假设 this 不可达,那意味着 this.thisReference 也不可达。这意味着 Foo 实例的 PhantomReference 将不符合入队条件。

(但是,它是 "safe",因为这不会抛出异常或使 JVM 崩溃,或者有任何其他不希望的 side-effects。)

不,存储对 this 的引用将不起作用:

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceTest {
    private final PhantomReference<Object> thisReference;

    public PhantomReferenceTest(ReferenceQueue<Object> queue) {
        thisReference = new PhantomReference<>(this, queue);
    }

    public static void main(String[] args) throws InterruptedException {
        test(false);
        System.out.println("\nVerify that reference is enqueued if gc of thisReference is prevented:");
        test(true);
    }

    private static void test(boolean keepRefToRef) throws InterruptedException {
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        ReferenceQueue<Object> verifyQueue = new ReferenceQueue<>();

        PhantomReference<?> refToRef = null;
        PhantomReferenceTest obj = new PhantomReferenceTest(queue);
        PhantomReference<?> verifyReference = new PhantomReference<>(obj, verifyQueue);

        if (keepRefToRef) {
            // Verify that reference is enqueued if it is kept alive
            refToRef = obj.thisReference;
        }

        obj = null;

        System.gc();
        verifyQueue.remove();
        System.out.println("Object was collected");
        System.out.println("thisReference was enqueued: " + (queue.poll() != null));

        // Pretend to use refToRef to make sure gc cannot collect it
        if (refToRef != null) {
            refToRef.get();
        }
    }
}

即使它会起作用,似乎也不能保证它在未来也会继续起作用。甚至不能保证一个字段在封闭的 class 之后被认为是 phantom-reachable。在Java 12.0.1中情况正好相反:

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class EnqueuingOrder {
    private static class RefToField extends PhantomReference<Object> {
        public RefToField(Object obj, ReferenceQueue<Object> queue) {
            super(obj, queue);
        }
    }

    private final Object field;

    public EnqueuingOrder() {
        field = new Object();
    }

    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Object> queue = new ReferenceQueue<>();

        EnqueuingOrder obj = new EnqueuingOrder();
        PhantomReference<?> refToObj = new PhantomReference<>(obj, queue);
        PhantomReference<?> refToField = new RefToField(obj.field, queue);
        obj = null;

        System.gc();

        System.out.println("First: " + queue.remove());
        System.out.println("Second: " + queue.remove());
    }
}

package documentation 包含误导性的短语:“一个对象是 幻影可达的 如果它既不是强可达的,也不是弱可达的,则它已经完成,并且一些虚幻的引用提到了它。”

“some phantom reference”并没有强调引用对象本身必须是可达的这一事实,这在Notification部分有说明:

The relationship between a registered reference object and its queue is one-sided. That is, a queue does not keep track of the references that are registered with it. If a registered reference becomes unreachable itself, then it will never be enqueued.

因为幻影可达没有实际后果,除了可能导致幻影引用排队外,这才是最重要的。

注意软可达性和弱可达性的定义比较好:

  • An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference.
  • An object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference. …

这里强调对象必须通过引用对象可达,也就是说引用对象也必须可达

以类似方式定义虚拟可达性的问题是,虚拟可达对象实际上是不可访问的,因为 PhantomReference 覆盖 get() 方法始终 return null,因此应用程序无法到达所指对象,因此无法遍历任何虚可达对象。

也许更好的定义是

  • An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, but can be reached by the garbage collector by traversing a phantom reference.

根据该定义,很明显您的带有 self-reference 的示例将不起作用,就像它不适用于 WeakReferenceSoftReference 一样。请注意,当您的 class 没有专用的 finalize() 方法时,使用 WeakReferencePhantomReference.

之间没有实际区别

似乎 API 设计者也没有完全理解其含义,因为 specification prior to Java 9 包含规则:

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.

它公然忽略了 幻影可达 对象不可达这一点,至少从应用程序的角度来看并保持它 幻影可达 从垃圾收集器的角度来看没有实际好处。不让对象再次活动,这就是最终确定的不同之处。但请注意,在此处,文档承认如果幻影引用本身变得不可访问,则引用对象将不再是 幻影可达.

从 Java9 开始,虚引用在入队时会自动清除,因此对象从 虚可达 过渡到 的唯一实际含义unreachable,是正在排队的幻象引用。这需要引用对象是可访问的。