为什么在重写 finalize 方法时引用不放入引用队列

why the reference don't put into reference queue when finalize method overrided

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

但是如果我删除了classA中的finalize方法,结果是:

java.lang.ref.PhantomReference@5b2c9e5d

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

非常有趣的观察。这是正在发生的事情:

当class有non-trivial(在OP情况下非空)finalize方法JVM将创建一个java.lang.ref.Finalizer(这是一个子class Reference) 对象并将其指向我们的对象,在本例中为 A 对象。这反过来将阻止 PhantomReference 将其入队,因为 A 已被 Finalizer.

引用

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

来自@Turing85 的观察是预期的,因为当我们删除 finalize 方法中的所有语句时,它变成 trivial 并且表现得像任何没有 finalize 方法的 class 和不会被 Finalizer.

引用

更新:

有人问 Finalizer 是否会完全清除它对 A 的引用。 JVM 会在随后的 GC 运行中清除它,这反过来最终允许 PhantomReference 将 A 排入其引用队列。

例如 运行 下面带有非平凡 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() );
}

打印:

finalize 
java.lang.ref.PhantomReference@15db9742

对于非平凡的 finalize,Java 在 finalize 运行之前知道对象无法访问*,但不知道对象 仍然finalize.

之后无法访问

它必须等待对象在另一个 GC 周期中再次被视为不可访问,然后才能将幻像引用排入队列。

*在java.lang.ref docs的术语中不是完全不可到达,但既不是强可达、软可达也不是弱可达