GC通过Phantom References不会收集类的内部字段

时间:2017-03-15 22:29:56

标签: java garbage-collection phantom-reference

当引用是类中的字段时,我遇到了幻影引用的问题。当类对象设置为null时,GC不会自动收集字段

Controller.java

public class Controller {
        public static void main( String[] args ) throws InterruptedException
    {
        Collector test = new Collector();
        test.startThread();

        Reffered strong = new Reffered();
        strong.register();
        strong = null;  //It doesn't work
        //strong.next =null;  //It works
         test.collect();
        Collector.m_stopped = true;
        System.out.println("Done");
    }
}

Collector.java:我有一个Collector,它将一个对象注册到引用队列,并在收集它时将其打印出来。

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import java.util.Map;

public class Collector {
        private static Thread m_collector;
        public static boolean m_stopped = false;
        private static final ReferenceQueue refque = new ReferenceQueue();
        Map<Reference,String> cleanUpMap = new HashMap<Reference,String>();
        PhantomReference<Reffered> pref;


        public void startThread() {
            m_collector = new Thread() {
                public void run() {
                    while (!m_stopped) {
                        try {
                                Reference ref = refque.remove(1000);
                                System.out.println(" Timeout ");
                                                    if (null != ref) {
                                System.out.println(" ref not null ");

                            }
                        } catch (Exception ex) {
                            break;
                        }
                    }
                }
            };
            m_collector.setDaemon(true);
            m_collector.start();
        }

        public void register(Test obj) {
            System.out.println("Creating phantom references");


              //Referred strong = new Referred();
              pref = new PhantomReference(obj, refque);
              cleanUpMap.put(pref, "Free up resources");

        }

       public static void collect() throws InterruptedException {
        System.out.println("GC called");
        System.gc();
        System.out.println("Sleeping");
        Thread.sleep(5000);
    }
}

Reffered.java

   public  class Reffered {

       int i;
       public Collector test;
       public Test next;

       Reffered () {
            test= new Collector();
            next = new Test();

       }
       void register() {
           test.register(next);
       }
   }

测试是一个空类。我可以看到&#34; next&#34;当Reffered对象设置为null时,不会收集Refferred类中的字段。换句话说,当&#34; strong&#34;设置为null,&#34; next&#34;没有收集。我认为&#34;接下来&#34;将由GC自动收集,因为&#34; next&#34;当&#34; strong&#34;时不再被引用设置为null。然而,当&#34; strong.next&#34;设置为null,&#34; next&#34;按照我们的想法收集。为什么&#34; next&#34;当strong设置为null时,不会自动收集?

1 个答案:

答案 0 :(得分:1)

您的代码结构非常混乱。

在代码的开头,您有语句

Collector test = new Collector();
test.startThread();

所以你要创建一个后台线程将引用的Collector实例。该线程甚至没有触及该引用,但由于它是一个匿名内部类,它将保存对其外部实例的引用。

Reffered中,您有一个类型为Collector的字段,该字段在构造函数中使用new Collector()初始化,换句话说,您正在创建另一个Collector实例。这是您调用register的实例。

因此register创建的所有工件,PhantomReference中保存的prefHashMap中保存的cleanUpMap所有工件都引用了{ {1}}仅由PhantomReference引用的Collector实例引用。如果Reffered实例无法访问,则所有这些工件也将无法访问,并且不会在队列中注册任何内容。

这是回忆java.lang.ref package documentation

的地方
  

注册参考对象与其队列之间的关系是片面的。也就是说,队列不会跟踪向其注册的引用。如果注册的引用本身无法访问,那么它将永远不会被入队。使用引用对象的程序负责确保只要程序对其所指对象感兴趣,对象就可以保持可达。

有一些方法可以说明您的计划存在的问题 不是Refferedstrong = null;,而是两者

strong.next = null;

这里,strong.next = null; strong = null; 被淘汰无关紧要,一旦next被执行,这个变量无论如何都无法到达。之后,只能通过strong = null实例访问的PhantomReference本身无法访问,并且不会打印“ref not null”消息。

或者,您可以将该代码部分更改为

Reffered

这也会使strong.next = null; strong.test = null; 无法到达,因此从未入队。

但如果你把它改成

PhantomReference

将打印消息“ref not null”,因为Object o = strong.test; strong = null; 持有对o的间接引用。必须强调的是,这不是保证行为,允许Java消除未使用的局部变量的影响。但它与当前的HotSpot实现具有足够的可重现性,以证明这一点。

最重要的是,PhantomReference实例始终按预期收集。只是在某些情况下,收集的内容比你所知的要多,包括Test本身,所以没有发生任何通知。

作为最后一点,必须在两个线程之间共享一个像PhantomReference这样的变量,以确保线程会注意到另一个线程所做的修改。它恰好在没有这里工作,因为JVM的优化器没有为这么短的运行程序和像x68同步缓存这样的架构做很多工作。但它不可靠。