我决定在一个单独的主题中继续https://stackoverflow.com/a/41998907/2674303。
让我们考虑以下示例:
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.");
}
}
Java 11打印以下内容:
finalizing the object in Thread[Finalizer,8,system]
weak reference enqueued after 1 gc polls
done finalizing.
phantom reference enqueued after 3 gc polls
前2行可以更改顺序。看起来它们是并行工作的。
最后一行有时会打印2个gc民意调查,有时会打印3
因此,我发现PhantomReference的入队需要更多的GC周期。怎么解释呢?在文档中某处提到了(我找不到)吗?
P.S。
WeakReference Java文档:
假设垃圾收集器在 物体难以到达的时间。那时它将 原子清除该对象的所有弱引用和所有弱对象 引用任何其他弱可达对象,从中 可通过一系列强引用和软引用来访问对象。在 同时它将声明所有以前弱可及的 要完成的对象。在同一时间或以后 将排队那些已注册的新近清除的弱引用 有参考队列
PhantomReference Java文档:
假设垃圾收集器在某个时间点确定 一个对象是幻影可达的。那时它将原子地 清除对该对象的所有幻像引用以及所有幻像引用 到该对象所在的任何其他幻影可到达对象 可达的。在同一时间或更晚的时间它会进入 那些已向其注册的新清除的幻像引用 参考队列
对我来说,差异还不清楚
我从@Holger得到了我的问题的答案:
他(没有性别歧视,但我想是的)向我指出了java doc,并注意到,与“软引用”和“弱引用”相比,PhantomReference包含额外的短语:
如果对象既不牢固也不柔软,则很难达到 可达,但可以通过遍历弱引用来达到。当。。。的时候 清除了对弱可达对象的弱引用,该对象 有资格完成。
如果对象是幻影可到达的 它既不是强力,柔弱或弱势可达的, 完成,并且一些幻影引用了它
我的下一个问题是什么意思已完成?我希望这意味着完成了finalize方法
为了证明这一点,我修改了应用程序,如下所示:
public class SimpleGCExample {
static SimpleGCExample object;
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(10000);
System.out.println("done finalizing.");
object = this;
}
}
我看到以下输出:
weak reference enqueued after 1 gc polls
finalizing the object in Thread[Finalizer,8,system]
done finalizing.
应用程序挂起。我认为这是因为对于弱/软引用,GC以下列方式工作:一旦GC检测到该对象是弱/软可访问对象,它就会并行执行两个操作 :
因此,要添加到ReferenceQueue中,对象是否已复活都没有关系。
但是对于PhantomReference,动作是不同的。一旦GC检测到该对象是“幻影可到达”,它将依次执行以下操作:
但是@Holger说它已经完成,这意味着JVM启动了 finalize()方法调用,对于将PhantomReference添加到ReferenceQueue而言,它是否完成还是无关紧要。不。但是看起来像我的例子表明这确实很重要。
坦白说,我不了解根据添加到弱引用和软引用的RefernceQueue中的区别。是什么主意?
答案 0 :(得分:2)
关键点是the package documentation中“ 幻影可达”的定义:
- 如果对象既不是强力,柔弱也不是弱力可到达的,则它是幻影可到达的,它已完成,并且某些幻像引用引用了它。
粗体强调我的
请注意,当我们删除finalize()
方法时,幻像引用将与弱引用一起被立即收集。
这是JLS §12.6的结果:
为了提高效率,实现可能会跟踪不覆盖Object类的finalize方法或以平凡的方式覆盖它的类。
…
我们鼓励实现将此类对象视为具有未覆盖的终结器,并更有效地完成它们,如§12.6.1中所述。
不幸的是,第12.6.1节并未涉及“拥有未被覆盖的终结器”的后果,但是很容易看出,该实现只是将那些对象视为已被终结,而从未将它们排队等待终结和因此,能够立即回收它们,这会影响典型Java应用程序中所有对象的大部分。
另一种观点是,对于具有琐碎终结器的对象,将省略确保finalize()
方法最终将被调用的必要步骤,即Finalizer
实例的创建和链接。另外,在Escape Analysis之后消除创建纯本地对象的操作仅适用于那些对象。
由于没有终结器的对象的弱引用和幻像引用之间在行为上没有区别,因此可以说,终结的存在及其复活对象的可能性是存在幻像引用的唯一原因。仅在可以安全地假定它不再复活时才能够执行对象的清理操作。
¹但是,在Java 9之前,这种安全性不是防弹的,因为幻象引用不会自动清除,并且允许深层反射来扭曲整个概念。
答案 1 :(得分:1)
PhantomReference
仅在任何关联的finalizer
完成执行后才入队。请注意,finalizer
可以使对象复活(由普林斯顿大学前安全互联网项目使用,效果很好)。
未指定超出规范的确切行为。这是实现相关的东西。
那似乎正在发生什么事情?一旦一个对象很难被收集,它也是可以最终确定的。因此,WeakReference
可以排入队列,并且对象可以在同一世界停止事件中排队等待最终确定。终结线程与ReferenceQueue
线程(主线程)并行运行。因此,您可能会以任意顺序看到输出的前两行,总是(除非大为延迟),然后是第三行。
finalizer
退出队列后,只有一段时间才能退出PhantomReference
。因此,gc计数严格更大。该代码看起来像是一场公平的竞赛。也许改变毫秒超时会改变事情。 GC的大多数事情都没有确切的保证。