【发布时间】:2019-06-21 14:16:44
【问题描述】:
我决定在一个单独的主题中继续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 文档:
假设垃圾收集器在某个时间点确定 对象弱可达的时间。到时候就会 原子地清除对该对象的所有弱引用和所有弱引用 对任何其他弱可达对象的引用 可以通过一系列强引用和软引用访问对象。在 同时它会声明所有以前的weakly-reachable 要最终确定的对象。在同一时间或稍后的某个时间 将注册的那些新清除的弱引用排入队列 带参考队列
PhantomReference java 文档:
假设垃圾收集器在某个时间点确定 一个对象是幻影可达的。那时它会原子地 清除对该对象的所有幻像引用和所有幻像引用 到该对象所在的任何其他幻影可达对象 可达。在同一时间或稍后的某个时间,它将排队 注册的那些新清除的虚拟引用 参考队列
区别对我来说不是很清楚
P.S.(我们说的是具有非平凡 finalize 方法的对象)
@Holger 回答了我的问题:
他(没有性别歧视,但我想是的)将我指向java doc,并注意到与软引用和弱引用相比,PhantomReference 包含额外的短语:
一个对象是弱可达的,如果它既不强也不软 可达,但可以通过遍历弱引用来达到。当。。。的时候 对弱可达对象的弱引用被清除,该对象 有资格进行最终确定。
一个对象是幻影可达的,如果 它既不是强烈的,柔和的,也不是微弱的可达性,它已经 最终确定了,并且一些幻像引用了它
我的下一个问题是关于它是什么意思已经完成我预计这意味着完成方法
为了证明这一点,我这样修改了应用程序:
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中,它是否完成或完成并不重要不是。但看起来我的例子表明它真的很重要。
坦率地说,我不明白添加到引用队列中以获取弱引用和软引用的区别。想法是什么?
【问题讨论】:
-
你不应该让问题以这种方式发展。除此之外,您插入的内容非常错误。当 finalize 方法还没有被调用时,对象不是幻象可达的。是否存在幻像引用与尚未执行(非平凡) finalize 方法的对象完全无关。并且垃圾收集器不会按顺序执行那些命名的操作。 您的代码会定期调用
System.gc()。一般情况下,finalize()完成后什么都不会发生。 -
@Holger 代码很糟糕 - 我同意,但我想知道为什么它的行为就像我看到的那样。代码调用(尝试)gc 只是为了检测如果现在开始 GC 会发生什么。
-
@Holger 我在实践中看到,GC 仅在 finalize 方法终止后才将幻影可达引用添加到 ReferenceQueue 中,即使它很长。你不同意。让我们先讨论这一点。你为什么这么认为?
-
我没有不同意。我说过,当
finalize()方法被调用时,对象被认为是最终确定的,但要幻像可达,它还必须是“既不强、不柔和、也不弱可达”。在实践中,这意味着方法必须完成,并且尤其是,这意味着对象不能逃逸。我从来没有说过不同的话。但是你一直忽略这一点。 -
在某个时间点,“既不强也不软可及”的条件得到满足,因此弱引用被清除并移交给入队,但幻像引用没有,因为对象尚未最终确定。然后,调用
finalize()方法,现在对象是finalized,但也是强可达的,所以第一个条件不满足,所以对象还是不是幻可达的。对象必须再次变得“既不强烈也不柔软可及”再次。正如我之前的评论中所展示的,这也适用于新创建的弱引用。
标签: java garbage-collection phantom-reference