Java:PhantomReference,ReferenceQueue和finalize

我有PR,PR指向的对象O,以及为PR设置的RQ。 我有一个线程继续轮询RQ,并在它在RQ中找到的第一个引用,线程打印它找到它的时间,并退出。

事情很好,但是当O有一个finalize(无论多么微不足道)的时候,线程不再在RQ中找到引用并且无限期地继续运行。

问题:为什么会这样? 我正在使用Sun JDK 1.6。

这是代码:

good case

 public class MyGCPhantom { public static void main(String[] args) throws InterruptedException { GCPhantomObject p = new GCPhantomObject(); ReferenceQueue phantomQueue = new ReferenceQueue(); PhantomReference pr = new PhantomReference(p, phantomQueue); new GCPhantomThread(phantomQueue, "Phantom").start(); p = null; System.gc(); } } class GCPhantomObject { @Override protected void finalize() { //System.out.println("GCPhantom finalized " + System.currentTimeMillis()); } } class GCPhantomThread extends Thread { private ReferenceQueue referenceQueue; private String name; GCPhantomThread(ReferenceQueue referenceQueue, String name) { this.referenceQueue = referenceQueue; this.name = name; } @Override public void run() { while(referenceQueue.poll() == null); System.out.println(name + " found at " + System.currentTimeMillis()); } } 

bad case

只需取消注释GCPhantomObject finalize()中的SOP GCPhantomObject

你的分析有点偏。 在好的情况和坏的情况下,您的对象都实现了finalize 。 在好的情况下,它实现了它; 在坏情况下,非平凡。 因此,显而易见的问题在于最终化的平凡和非平凡实现之间的区别。

我认为没有理由为什么JVM会被规范强制排队你的引用。 您执行单个GC运行,然后继续等待某些事情发生。 众所周知,任何非平凡的终结器都可以复活对象,因此在排队之前可能需要更多的GC周期。 我建议添加更多GC调用。

另请注意,不建议您决定使用poll而不是remove 。 您应该使用阻止调用来阻止忙碌轮询。

作为参考,这些是文档中的相关定义:

如果垃圾收集器在某个时间点确定幻像引用的引用是幻像可达的,那么在那个时间或稍后它将使引用入队。


如果一个对象既没有强烈,柔和,也没有弱到达,那么该对象是幻像可达的,它已经完成,并且一些幻像引用指的是它。


已完成的对象已自动调用其终结器。

在完成对象之后,Phantom Reference才会出现在ReferenceQueue中。 你正在做一个繁忙的循环,所以它是有问题的。 请注意,最终确定至少需要两个gcs。

我刚试过我系统上发布的代码,即使经过两次System.gc()调用也无法正常工作。 即使System.gc()在此处调用GCPhantomThread类的while循环,它也不会终止。

在我看来,这里的问题是你正在创建的对象永远不会被放置在ReferenceQueue中,因为当GCPhantomThread运行时它甚至不能进行幻像可达。 main()方法中对象的PhantomReference超出范围,因此当您运行GCPhantomThread时,该对象甚至不能进行幻像访问。 根据文档,对于要引入的幻影引用,最终化和幻像可达性是必要的。

当我将幻像引用传递给GCPhantomThread时,它可以工作。 在我的机器上,此代码始终终止:

 import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.util.ArrayList; import java.util.List; import java.util.Random; public class MyGCPhantom { public static void main(String[] args) throws InterruptedException { GCPhantomObject p = new GCPhantomObject(); ReferenceQueue phantomQueue = new ReferenceQueue(); PhantomReference pr = new PhantomReference(p, phantomQueue); new GCPhantomThread(pr, phantomQueue, "Phantom").start(); p = null; pr = null; System.gc(); System.out.println("main thread done ..."); } } class GCPhantomObject { @Override protected void finalize() { System.out.println("GCPhantom finalized at " + System.nanoTime()); } } class GCPhantomThread extends Thread { private ReferenceQueue referenceQueue; private String name; private PhantomReference pr; GCPhantomThread(PhantomReference pr, ReferenceQueue referenceQueue, String name) { this.referenceQueue = referenceQueue; this.name = name; this.pr = pr; } @Override public void run() { try { while (referenceQueue.remove(5000) == null) { System.gc(); } System.out.println(name + " found at " + System.nanoTime()); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } 

Jack Shirazi在Finalizer上找到了一篇很棒的文章。 一旦我们浏览了这篇文章,问题就会回答。

http://www.fasterj.com/articles/finalizer1.shtml

简而言之:在一个非常重要的finalize()方法的情况下,即使在第一次GC运行中“收集”了对象,它也不会在那时被物理删除。 这发生在下一次GC运行中。 这就是PR对象在第二次GC期间出现在队列中的原因。