为什么我无法从ReferenceQueue获取可终结对象的PhantomReference?

5
这是我的代码。
public class FinalizableObject {

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize() invoked for " + this);
        super.finalize();
    }
}

public class Main {
    private static void test() throws InterruptedException {
        ReferenceQueue<FinalizableObject> rq = new ReferenceQueue<FinalizableObject>();
        FinalizableObject obj = new FinalizableObject();
        PhantomReference<FinalizableObject> pr1 = new PhantomReference<FinalizableObject>(obj, rq);
        obj = null;
        System.gc();
        Reference<? extends Object> ref = rq.remove();
        System.out.print("remove " + ref + " from reference queue\n");
    }
    public static void main(String[] args) {
        try {
            test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

非常奇怪,rq.remove() 将永远被阻塞。为什么我的终结对象的虚引用不能放入引用队列中?它已经被GC收集了吗?

1个回答

1

问题出在你的非平凡finalize()方法上。在默认实现(在Object类中)中,该方法实际上是空的。当它的实现不为空时,不能保证对象在调用finalize()后立即被收集。

如果你按照以下方式修改程序:

    for (int i = 0; i  < 1000; i++)
        System.gc();

例如,如果您多次调用GC,则可能会导致rq对象被完全收集(在我的机器上进行测试)。

此外,我建议您参考以下链接:

  1. Java: PhantomReference、ReferenceQueue和finalize
  2. 发现具有非平凡终结器的对象
  3. 终结器的秘密生活:第2页

UPD:另一种方式:您必须记住,非平凡finalize()方法由特殊的Finalizer-Thread调用,该线程也必须被收集。因此,为了完全收集pr,您可以采取以下措施:

a)在Main方法中创建标志:

public static volatile boolean flag;

finalize()方法中设置标志:

@Override
protected void finalize() throws Throwable {
    System.out.println("finalize() invoked for " + this);
    super.finalize();
    Main.flag = true;
}

c) 检查标志是否为真,然后再次调用 gc()

    System.gc();
    while (!flag) Thread.sleep(10);
    System.gc();

非常感谢。我知道FinalizerReferenceQueue和FinalizerDeamon,2次GC可以完全收集一个可终结对象。但我不明白为什么100次GC会触发它被收集(即使20次GC也无法触发入队)。另一方面,如果我用WeakReference替换PhantomReference,它可以很快地删除引用。 - Devboard Fan
@DevboardFan 这是因为对于调用非平凡的finalize()方法,JVM构造了特殊的Finalizer线程,这个线程也必须被收集和完成。 - Andremoniy
@DevboardFan 顺便说一下,你可以在 Main() 类中创建 public static volatile boolean flag;,在 finalize() 方法中设置此标志,然后在 Main() 中等待,直到此标志被设置(在第一个 .gc() 后)。之后,您可以再调用一次 System.gc()。这就足够了。 - Andremoniy
我明白了。你的意思是在进行1次垃圾回收后,finalize()方法会被调用,但对象实际上尚未从堆中被彻底回收。对我来说,为什么需要这么多次GC来触发真正的回收过程(而不是调用finalize())还有点奇怪。再次感谢你。 - Devboard Fan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接