也许,以下程序会有所帮助:
public class SimpleGCExample {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> queue=new ReferenceQueue<>();
SimpleGCExample e = new SimpleGCExample();
Reference<Object> pRef=new PhantomReference<>(e, queue),
wRef=new WeakReference<>(e, queue);
e = null;
for(int count=0, collected=0; collected<2; ) {
Reference ref=queue.remove(100);
if(ref==null) {
System.gc();
count++;
}
else {
collected++;
System.out.println((ref==wRef? "weak": "phantom")
+" reference enqueued after "+count+" gc polls");
}
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalizing the object in "+Thread.currentThread());
Thread.sleep(100);
System.out.println("done finalizing.");
}
}
在我的系统上,它打印
weak reference enqueued after 1 gc polls
finalizing the object in Thread[Finalizer,8,system]
done finalizing.
phantom reference enqueued after 2 gc polls
或者
finalizing the object in Thread[Finalizer,8,system]
weak reference enqueued after 1 gc polls
done finalizing.
phantom reference enqueued after 2 gc polls
由于多线程,前两条消息的顺序有时会有所不同。有时,幻像引用在 3 次轮询后被报告入队,这表明它花费了超过指定的 100 毫秒。
关键是
- 软引用和弱引用在开始完成之前或正确时被清除并入队
- 幻影引用在finalization之后入队,假设对象没有泄露
finalize
方法,否则在对象再次变得不可访问后入队
- (非平凡的)
finalize()
方法的存在导致需要至少一个额外的垃圾收集周期来检测对象不可访问或再次幻像可访问
由于超过 99% 的对象不需要终结,因此强烈建议所有 JVM 供应商检测何时finalize()
未被覆盖或“微不足道”,即空方法或单独super.finalize()
调用。在这些情况下,应该省略最终确定步骤。finalize()
通过删除上面示例中的方法,您可以轻松地检查此优化是否发生在您的 JVM中。然后它打印
weak reference enqueued after 1 gc polls
phantom reference enqueued after 1 gc polls
由于两者都同时入队并以任意顺序检索,因此两条消息的顺序可能不同。但是它们总是在一个 gc 循环后都入队。
值得注意的是,幻影引用不会自动清除,这意味着它需要另一个垃圾回收周期,直到对象的内存真正可以被重用,所以上面的例子需要至少三个周期使用非平凡finalize()
方法和两个没有. Java 9 将改变这一点,自动清除幻像引用,因此在上面的示例中,在内存真正可以回收之前,它需要两个循环完成,一个没有完成。好吧,准确地说,在这个简单的例子中,对象的内存永远不会被回收,因为程序在这之前就终止了。
上面的代码还演示了 Reference API 的预期用例之一。我们可以使用它来检测对象的可达性何时在我们完全控制的代码中发生变化,例如在main
方法中使用循环。相反,finalize()
可以在任意时间由不同的、未指定的线程调用。该示例还表明,您可以从参考对象中提取信息,而无需该get()
方法。
实际应用程序经常使用引用类的子类来向它们添加更多信息。这就是WeakHashMap.Entry
扩展WeakReference
并记住哈希码和值的情况。清理可以在法线贴图操作中完成,不需要任何线程同步。这对于方法来说是不可能的finalize()
,除了 map 实现不能将finalize()
方法推送到键的类中。
这意味着“比最终确定更灵活”一词。
WeakHashMap
演示了该方法get()
如何有用。只要 key 没有被收集,它就会被报告为在 map 中,并且可以在遍历所有 key 或条目时检索到。
该PhantomReference.get()
方法已被覆盖以始终返回null
,以防止应用程序可以恢复入队引用的引用。这是“幻像引用不会自动清除”规则的直接后果。这条规则本身是有问题的,它的初衷是在黑暗中。虽然该规则即将在下一个 Java 版本中更改,但恐怕get()
会继续始终返回null
向后兼容。