当引用是类中的字段时,我遇到了幻影引用的问题。当类对象设置为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时,不会自动收集?
答案 0 :(得分:1)
您的代码结构非常混乱。
在代码的开头,您有语句
Collector test = new Collector();
test.startThread();
所以你要创建一个后台线程将引用的Collector
实例。该线程甚至没有触及该引用,但由于它是一个匿名内部类,它将保存对其外部实例的引用。
在Reffered
中,您有一个类型为Collector
的字段,该字段在构造函数中使用new Collector()
初始化,换句话说,您正在创建另一个Collector
实例。这是您调用register
的实例。
因此register
创建的所有工件,PhantomReference
中保存的pref
和HashMap
中保存的cleanUpMap
所有工件都引用了{ {1}}仅由PhantomReference
引用的Collector
实例引用。如果Reffered
实例无法访问,则所有这些工件也将无法访问,并且不会在队列中注册任何内容。
这是回忆java.lang.ref
package documentation:
注册参考对象与其队列之间的关系是片面的。也就是说,队列不会跟踪向其注册的引用。如果注册的引用本身无法访问,那么它将永远不会被入队。使用引用对象的程序负责确保只要程序对其所指对象感兴趣,对象就可以保持可达。
有一些方法可以说明您的计划存在的问题
不是Reffered
或strong = 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同步缓存这样的架构做很多工作。但它不可靠。