对“ this”进行幻影引用是否安全?

时间:2019-06-02 11:11:39

标签: java phantom-reference

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

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

class Foo {
    private PhantomReference<Foo> thisReference;

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

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

文档说:

  

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

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

3 个答案:

答案 0 :(得分:3)

我认为您的工作不会奏效。

幻影可达,对象必须具备以下条件:

  • 该对象不可强行到达,可柔和地到达或弱可达,
  • 对象已完成,并且
  • 该对象可通过至少一条带有幻像引用的路径从GC根目录到达。

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

(但是,从某种意义上讲,这不会引发异常或使JVM崩溃,也不会造成任何其他不良影响,这是“安全的”。)

答案 1 :(得分:2)

package documentation包含一个令人误解的短语:“一个对象是幻影可到达的,如果它既不是强,软,也不是弱可达的,则已完成,并且某些幻像引用是指它。”

“某些幻像引用”并未强调引用对象本身必须可到达的事实,这在 Notification 部分中说明:

  

已注册参考对象与其队列之间的关系是单方面的。即,队列不跟踪向其注册的引用。如果注册参考本身无法访问,那么它将永远不会排队。

由于幻影可达没有实际后果,除了可能导致幻影引用排队外,这就是所有问题。

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

  
      
  • 如果对象不是很容易到达,但是可以通过遍历软引用来到达,则它是可以轻易到达
  •   
  • 如果对象既不是强也不能软到达,但可以通过遍历弱引用来到达,则它是<弱>可到达。 …
  •   

这里,要强调的是对象必须通过参考对象可以到达,这意味着参考对象也必须可以到达。

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

也许会有更好的定义

  
      
  • 一个对象是幻影可到达,如果它既不是强,软,也不是弱可达,它已经完成了确定,但是可以通过遍历一个由垃圾收集器到达幻影参考。
  •   

有了这个定义,很明显您的带有自引用的示例将不起作用,就像它不适用于WeakReferenceSoftReference一样。请注意,当您的班级没有专用的finalize()方法时,使用WeakReferencePhantomReference之间没有实际区别。

似乎specification prior to Java 9包含以下规则,即使是API设计人员也无法完全理解其含义:

  

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

公然忽略了幻影可达对象不可访问的观点,至少从应用程序的角度来看,并从垃圾收集器的角度将其保持为幻影可达没有实际的好处。不使对象再次存活,对完成而言是非常不同的。但是请注意,在这个地方,文档承认,如果幻像引用本身变得不可访问,则指称对象将不再是幻影可访问

从Java 9开始,幻象引用在排队后会自动清除,因此对象从 phantomreachable 过渡到 unreachable 的唯一实际含义是入队。这就要求参考对象是可到达的。

答案 2 :(得分: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());
    }
}