3

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

由于ReferenceQueues 不跟踪为它们注册的引用(source,“通知”部分),我的想法是将引用存储为被跟踪对象的字段:

class Foo {
    private PhantomReference<Foo> thisReference;

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

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

文档说:

假设垃圾收集器在某个时间点确定对象是幻影可达的。那时,它将自动清除对该对象的所有幻像引用以及对该对象可从中访问的任何其他幻像可访问对象的所有幻像引用。在同一时间或稍后的某个时间,它会将那些在引用队列中注册的新清除的幻像引用排入队列。

但它没有提到垃圾收集是否可以在引用入队之前发生。

4

3 回答 3

3

我不认为你正在做的事情会奏效。

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

  • 对象不是强可达、软可达或弱可达,
  • 该对象已完成,并且
  • 该对象可以通过至少一个带有幻像引用的路径从 GC 根访问。

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

(但是,它是“安全的”,因为它不会引发异常或使 JVM 崩溃,或有任何其他不良副作用。)

于 2019-06-02T12:24:17.127 回答
3

文档包含误导性短语:“如果一个对象既不是强、软或弱可到达的,它已被最终确定,并且某些幻象引用引用它,则它 是幻象可到达的。”</p>

“一些幻像引用”并没有强调引用对象本身必须是可访问的这一事实,这在通知部分中有说明:

注册的引用对象与其队列之间的关系是片面的。也就是说,队列不会跟踪向其注册的引用。如果已注册的引用本身变得无法访问,那么它将永远不会入队。

由于幻影可访问没有实际后果,除了可能导致幻影引用排队之外,这才是最重要的。

请注意,软可达性和弱可达性的定义更好:

  • 如果一个对象不是强可达但可以通过遍历软引用来达到,那么它就是软可达的。
  • 一个对象是弱可达的,如果它既不是强可达也不是软可达,但可以通过遍历弱引用来达到。…</li>

这里强调的是对象必须是通过引用对象可达的,这意味着引用对象也必须是可达的。

以类似方式定义幻影可达性的问题在于,幻影可达对象实际上是不可达的,因为PhantomReference覆盖get()方法总是返回null,因此应用程序无法到达所指对象,因此不会遍历任何幻影可达对象。

也许,更好的定义是

  • 如果一个对象既不是强、软或弱可到达的,它已经被最终确定,但可以被垃圾收集器通过遍历一个幻象引用来到达,那么它就是幻象可到达的。

使用该定义,很明显您的带有自引用的示例将不起作用,就像它不适用于 a WeakReferenceor一样SoftReference。请注意,当您的类没有专用方法时,使用 a或 afinalize()之间没有实际区别。WeakReferencePhantomReference

似乎即使是 API 设计者也没有完全理解其中的含义,因为Java 9 之前的规范包含以下规则:

与软引用和弱引用不同,幻像引用在排队时不会被垃圾收集器自动清除。通过幻像引用可访问的对象将保持不变,直到所有此类引用都被清除或自身变得不可访问。

至少从应用程序的角度来看,它公然忽略了无法访问的幻象可访问对象这一点,并且从垃圾收集器的角度保持幻象可访问并没有实际好处。不再使对象生效,这与最终确定非常不同。但是请注意,在这个地方,文档承认如果幻像引用本身变得不可访问,则所指对象将停止幻像可访问

从 Java 9 开始,幻像引用在入队时会自动清除,因此对象从幻像可达到不可达的转换的唯一实际含义是被入队的幻像引用。这要求引用对象是可访问的。

于 2019-06-20T17:23:28.763 回答
0

不,存储对的引用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();
        }
    }
}

即使它会起作用,似乎也不能保证它在未来也会继续起作用。甚至不能保证一个字段在封闭类之后被认为是虚拟可达的。在 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());
    }
}
于 2019-06-16T18:22:27.507 回答