Java:PhantomReference,ReferenceQueue和finalize

时间:2012-10-17 11:19:00

标签: java garbage-collection phantom-reference

我有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<GCPhantomObject> pr = new PhantomReference<GCPhantomObject>(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

只需在finalize()的{​​{1}}中取消注释SOP。

4 个答案:

答案 0 :(得分:4)

您的分析有些偏差。在两者好的情况和坏的情况下,您的对象实现finalize。在好的情况下,它实现了它;在坏情况下,非平凡。因此,明显的问题在于finalize的简单和非平凡实现之间的区别。

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

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

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

  

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


  

如果一个物体既没有强烈,柔和,也没有微弱的可触及,它已被最终确定,并且某些幻像参考指的是它。


  

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

我刚试过我系统上发布的代码,即使经过两次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();
            }
        }
    }

答案 3 :(得分:0)

Jack Shirazi发表了一篇关于Finalizer的精彩文章。一旦我们浏览了这篇文章,问题就会自行解决。

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

简而言之: 在非常重要的finalize()方法的情况下,即使收集对象&#39;在第一次GC运行中,当时没有物理删除。这发生在下一次GC运行中。这就是PR对象在第二次GC期间出现在队列中的原因。