1

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

控制器.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:我有一个收集器,它将一个对象注册到引用队列并在收集时打印它。

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);
    }
}

引用.java

   public  class Reffered {

       int i;
       public Collector test;
       public Test next;

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

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

测试是一个空类。我可以看到当 Reffered 对象设置为 null 时,不收集 Refferred 类中的“下一个”字段。换句话说,当“strong”设置为空时,不收集“next”。我假设“next”将由 GC 自动收集,因为当“strong”设置为 null 时不再引用“next”。但是,当“strong.next”设置为null时,“next”就如我们所想的那样被收集。为什么strong设置为null时不会自动收集“next”?

4

1 回答 1

1

你有一个非常混乱的代码结构。

在代码的开头,您有语句

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

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

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

register因此,所有由、PhantomReferencehold inprefHashMaphold in创建的工件cleanUpMap,也有对 的引用PhantomReference,只被引用的实例Collector引用Reffered。如果Reffered实例变得无法访问,所有这些工件也变得无法访问,并且不会在队列中注册任何内容。

这是调用java.lang.ref包文档的地方:

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

有一些方法可以说明您的程序的问题。
而不是做任何一个,strong = null;strong.next = null;,你可以做两个

strong.next = null;
strong = null;

在这里,已经被清空没关系next,这个变量无论如何是无法访问的,一旦strong = null被执行。之后,PhantomReference只能通过Reffered实例访问的对象本身变得无法访问,并且不会打印“ref not null”消息。

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

strong.next = null;
strong.test = null;

这也将使PhantomReference无法访问,因此永远不会排队。

但是如果你把它改成

Object o = strong.test;
strong = null;

消息“ref not null”将被打印为o持有对PhantomReference. 必须强调的是,这不是保证行为,Java 允许消除未使用的局部变量的影响。但它在当前的 HotSpot 实现中具有足够的可重复性来证明这一点。


底线是,Test实例总是按预期收集。只是在某些情况下,收集的内容比您意识到的要多,包括它PhantomReference本身,所以没有发生任何通知。

作为最后一点,public static boolean m_stopped必须声明像您在两个线程之间共享的变量,volatile以确保一个线程会注意到另一个线程所做的修改。它碰巧在这里没有工作,因为 JVM 的优化器没有为如此短的运行程序和 x68 同步缓存等架构做太多工作。但这是不可靠的。

于 2017-03-16T15:31:42.410 回答