5

我有一个 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

只需取消注释 中的 SOPfinalize()即可GCPhantomObject

4

4 回答 4

4

你的分析有点不对劲。在好的情况和坏的情况下,您的对象都会finalize实现。在好的情况下,它实现起来很简单;在糟糕的情况下,非常重要。因此,显而易见的问题在于finalize.

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

另请注意,不建议您决定使用pollremove不是。您应该使用阻塞呼叫来防止忙轮询。

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

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


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


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

于 2012-10-17T11:45:36.093 回答
1

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

于 2012-10-17T11:32:28.153 回答
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();
            }
        }
    }

于 2012-10-19T14:07:44.790 回答
0

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

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

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

于 2017-10-31T14:05:15.027 回答