为什么引用不会在finalize方法覆盖时放入引用队列

时间:2018-01-09 11:57:10

标签: java reference finalize

public class Test {
    public static void main(String[] args) throws Exception {
        A aObject = new A();

        ReferenceQueue<A> queue = new ReferenceQueue<>();
        PhantomReference<A> weak = new PhantomReference<>(aObject, queue);

        aObject = null;
        System.gc();

        TimeUnit.SECONDS.sleep(1);

        System.out.println(queue.poll());
    }
}

class A{
    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();

        System.out.println("finalize");
    }
}

结果是:

finalize
null

但是如果我删除A类中的finalize方法,结果是:

java.lang.ref.PhantomReference@5b2c9e5d

所以,结果显示当我覆盖finalize方法时,弱对象没有被放入引用队列,那是因为aObject复活了吗?但我在finalize方法中没有做任何事情

2 个答案:

答案 0 :(得分:4)

对于一个非常重要的finalize,Java知道在finalize运行之前对象是无法访问的*,但是在{{{{}}之后它还不知道对象仍然无法访问1}}。

必须等待该对象在另一个GC循环中再次被视为无法访问,然后才能将幻像引用排入队列。

*在java.lang.ref docs的术语中并不完全无法达到,但既不强烈,轻柔,也无法达到

答案 1 :(得分:3)

非常有趣的观察。以下是发生的事情:

当类具有non-trivial(在OP情况下为非空)finalize方法时,JVM将创建java.lang.ref.Finalizer(它是Reference)对象的子类并指向它对我们的指称,在这种情况下是一个对象。这反过来会阻止PhantomReference将其排入队列,因为Finalizer已经引用了A. {/ p>

这是我在使用Java 1.8的调试器中的观察者,并且还详细描述了here

@ Turing85的观察是预期的,因为当我们删除finalize方法中的所有语句时,它变为trivial并且行为就像任何没有finalize方法的类一样,并且不会被{{{ 1}}。

<强>更新

有人询问Finalizer是否会明确其对A的引用。 JVM会在随后的GC运行中清除它,最终允许Finalizer将A排入其引用队列。

例如,使用非平凡的PhantomReference方法运行以下代码将从其引用队列中获取非空finalize

PhantomReference

打印:

public static void main(String[] args) throws Exception {
    A aObject = new A();

    ReferenceQueue<A> queue = new ReferenceQueue<>();
    PhantomReference<A> pr = new PhantomReference<>(aObject, queue);

    aObject = null;
    System.gc();

    TimeUnit.SECONDS.sleep(1);

    System.gc();

    TimeUnit.SECONDS.sleep(1);

    System.out.println( queue.poll() );
}