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

Inner fields of a class are not collected by GC via Phantom References

当引用对象是 class 中的字段时,我在使用 Phantom References 时遇到问题。当 class 对象设置为 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:我有一个收集器,它向引用队列注册一个对象,并在收集时打印它。

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

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

您的代码结构非常混乱。

在代码的开头,您有语句

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

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

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

的实例

因此 register 创建的所有工件,pref 中保存的 PhantomReferencecleanUpMap 中保存的 HashMap,它也引用了PhantomReference 仅由 Reffered 引用的 Collector 实例引用。如果 Reffered 实例变得不可访问,所有这些工件也将变得不可访问,并且队列中不会注册任何内容。

这里是回忆的地方 java.lang.ref package documentation:

The relationship between a registered reference object and its queue is one-sided. That is, a queue does not keep track of the references that are registered with it. If a registered reference becomes unreachable itself, then it will never be enqueued. It is the responsibility of the program using reference objects to ensure that the objects remain reachable for as long as the program is interested in their referents.

有一些方法可以说明您的程序存在的问题。
您可以 both:

而不是 strong = null;strong.next = null;
strong.next = null;
strong = null;

这里,next被null掉没关系,反正这个变量是不可达的,一旦strong = null被执行了。在那之后,只能通过 Reffered 实例访问的 PhantomReference 本身变得无法访问,并且不会打印“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 同步缓存这样的架构做太多工作。但是不靠谱