我决定在一个单独的主题中继续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 周期。怎么解释?它是否在文档中的某处提到(我找不到)?
附言
弱参考 java 文档:
假设垃圾收集器在某个时间点确定一个对象是弱可达的。那时,它将原子地清除对该对象的所有弱引用以及对通过强引用和软引用链可以访问该对象的任何其他弱可达对象的所有弱引用。同时它将声明所有以前的弱可达对象都是可终结的。在同一时间或稍后的某个时间,它会将那些注册到引用队列中的新清除的弱引用排入队列
PhantomReference java 文档:
假设垃圾收集器在某个时间点确定对象是幻影可达的。那时,它将自动清除对该对象的所有幻像引用以及对该对象可从中访问的任何其他幻像可访问对象的所有幻像引用。在同一时间或稍后的某个时间,它会将那些在引用队列中注册的新清除的幻像引用排入队列
区别对我来说不清楚
PS(我们正在谈论具有非平凡终结方法的对象)
我从@Holger 那里得到了我的问题的答案:
他(没有性别歧视,但我想是的)向我指出了java 文档,并注意到 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 检测到该对象是弱/软可到达的,它就会并行执行 2 个操作:
- 将 Weak/Soft 排入已注册的 ReferenceQueue 实例
- 运行 finalize 方法
因此,对于添加到 ReferenceQueue 中,对象是否复活并不重要。
但是对于 PhantomReference 操作是不同的。一旦 GC 检测到该对象是 Phantom Reachable,它就会按顺序执行以下操作:
- 运行 finalize 方法
- 检查该对象仍然只有 phantomReachable(检查该对象在 finalize 方法执行期间没有复活)。并且只有当对象是 GC 时才会将幻像引用添加到 ReferenceQueue
但是@Holger说它已经完成意味着JVM启动了finalize()方法调用,并且为了将PhantomReference添加到ReferenceQueue中,它是否完成并不重要。但看起来我的例子表明它真的很重要。
坦率地说,我不明白根据添加到参考队列中以获取弱参考和软参考的区别。想法是什么?