【问题标题】:Java: PhantomReference, ReferenceQueue and finalizeJava:PhantomReference、ReferenceQueue 和 finalize
【发布时间】:2012-10-17 11:19:00
【问题描述】:

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

一切正常,但当 O 完成最终确定(无论多么微不足道)时,线程不再在 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

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

【问题讨论】:

  • 不调用 super.finalize() 可能是问题的一部分(尽管在好的情况下你也不会这样做)。

标签: java garbage-collection phantom-reference


【解决方案1】:

你的分析有点不对劲。在 both 中,您的对象实现了finalize。在好的情况下,它实现起来很简单;在糟糕的情况下,非常重要。因此,显而易见的问题在于finalize 的简单实现与非平凡实现之间的区别。

我看不出为什么 JVM 会被规范强制让你的 ref 入队。您执行一次 GC 运行,然后继续等待某些事情发生。众所周知,任何重要的终结器都可能使对象复活,因此在入队之前可能需要更多的 GC 周期。我建议添加更多的 GC 调用。

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

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

如果垃圾收集器在某个时间点确定幻影引用的所指对象是幻影可到达的,那么在那个时间或稍后的某个时间它将将该引用加入队列。


如果一个对象既不是强、软或弱可达的,它已经被最终确定,并且一些幻像引用引用它,则它是幻像可达的。


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

【讨论】:

  • 很好,但它仍然没有回答我的问题。为什么它会以这种方式发生?即使我用像'int y = 0;'这样简单的东西替换那个SOP,我也会看到同样的问题
  • 我首先用 remove() 替换了 poll(),没有帮助。然后我用那个'int ...'替换了SOP,没有帮助。当我使 finalize() 变得微不足道(空体)时,它的工作与我使用 poll() 还是 remove() 无关。
  • 嗯,使用了两个 gc 调用,它们之间没有任何延迟。感谢您的解释。
【解决方案2】:

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

【讨论】:

  • 很好。我拥有的 finalize() 是微不足道的。那么为什么运行 finalize() 之后 PR 没有出现在 RQ 中呢?
  • 我认为那是因为你在finalization之后没有第二个GC
【解决方案3】:

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

【讨论】:

  • main() 中的对象只有在调用 GC 时才能幻像到达,因为堆栈帧还没有弹出,所以对于 GC,它只看到该对象的 PR 并因此收集它。 PR 超出范围 main() 返回后,而不是在 GC 期间。就您而言,您使用的是 Sun JDK 6 吗?我一直都有这种行为。
【解决方案4】:

发现 Jack Shirazi 的一篇关于 Finalizer 的优秀文章。阅读完这篇文章后,问题就会自行回答。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-05
    • 2013-10-05
    • 2010-09-06
    • 2012-03-08
    • 1970-01-01
    • 1970-01-01
    • 2013-11-18
    相关资源
    最近更新 更多