Mac*_*iej 3 java garbage-collection reference phantom-reference
我一直在阅读有关PhantomReference https://www.baeldung.com/java-phantom-reference的文章,并在此处找到了简化的示例代码:
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
Object object = new Object();
PhantomReference<Object> phantomReference = new PhantomReference<>(object, referenceQueue);
object = null;
System.gc();
Thread.sleep(1_000);
System.out.println("isEnqueued() after GC: " + phantomReference.isEnqueued());
Reference reference = referenceQueue.poll();
if(reference != null) {
System.out.println("isEnqueued() after poll(): " + phantomReference.isEnqueued());
}
}
Run Code Online (Sandbox Code Playgroud)
输出如下:
isEnqueued() after GC: true
isEnqueued() after poll(): false
Run Code Online (Sandbox Code Playgroud)
因此,一切工作都按预期进行,将对对象的强引用设置为null(GC会将其检测到)并将幻像引用添加到队列中。
现在在那篇文章中,他们说:“垃圾收集器在执行其引用的finalize方法之后,将一个幻像引用添加到引用队列。这意味着该实例仍在内存中。”
所以我想进行测试并覆盖finalize方法,例如:
Object object = new Object() {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize()");
}
};
Run Code Online (Sandbox Code Playgroud)
但随后输出有所不同,幻像引用不再添加到队列中:
finalize()
isEnqueued() after GC: false
Run Code Online (Sandbox Code Playgroud)
有人可以解释为什么在此更改输出之后不同,以及如何更改此代码,以便将幻像引用添加到队列中?
我已经在JDK 8和11上对此进行了测试,两个平台上的结果相同。
语句“垃圾收集器在执行其引用的finalize方法后将一个幻像引用添加到引用队列。”这充其量不过是草率的。
您应该参考规格:
如果垃圾收集器在某个时间点确定幻像引用的参照对象是幻像可到达的,则在那时或以后的某个时间,它将使该引用入队。
鉴于“幻影可达”的链接定义为:
如果对象既不是强的,软的也不是弱的,并且已被终结,并且有一些幻像引用对其引用,则它是幻像可到达的。
因此,如果仅通过幻像引用进行引用,则对象“在执行其参照对象的finalize方法之后”是幻像可到达的,因此在此之后将被排队,但不会立即被排队。由于对象在执行其finalize()方法期间是高度可访问的,因此它至少需要一个额外的垃圾回收周期才能检测到它是幻像可访问的。然后,“在那个时候或以后的某个时间”它将被排队。
如果将程序更改为
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
Object object = new Object() {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize()");
}
};
PhantomReference<Object> phantomReference = new PhantomReference<>(object, referenceQueue);
object = null;
System.gc();
Thread.sleep(1_000);
System.gc();
Thread.sleep(1_000);
System.out.println("isEnqueued() after GC: " + phantomReference.isEnqueued());
Reference reference = referenceQueue.poll();
if(reference != null) {
System.out.println("isEnqueued() after poll(): " + phantomReference.isEnqueued());
}
Run Code Online (Sandbox Code Playgroud)
您很可能会看到所需的输出,但是需要强调的是,不能保证垃圾收集器在您调用时会真正运行,System.gc()或者在垃圾回收运行一定时间后会完成,或者无法找到所有垃圾回收器。特定周期内的对象。此外,入队在gc周期之后异步发生,因此,即使到垃圾收集器完成并检测到特殊的可达性状态时,也可能要经过额外的时间才能入队。
请注意,“它暗示实例仍在内存中。”这句话也无法正确理解,但是在这种情况下,它是基于对Java核心开发人员的误解。
创建API时,在规范中添加了一个句子,即使在Java 8版本中也可以找到:
与软引用和弱引用不同,幻象引用在排队时不会被垃圾收集器自动清除。通过幻像引用可访问的对象将保留,直到清除所有此类引用或它们自身无法访问为止。
这可能会导致天真的假设,即该对象仍必须在内存中,但是Java®语言规范指出:
可以设计程序的优化转换,以将可到达的对象数量减少到少于天真的被认为可到达的对象数量。
简而言之,如果程序的行为不变,则对象的内存可能会更早地回收。这尤其适用于应用程序根本无法使用对象(如幻像引用)的情况。如果对象不再位于内存中,则程序的行为将不会改变,因此您不能假定它实际上是。
这就引出了一个问题,为什么不完全删除幻象引用的规则被添加到了规范中。如该答案中所讨论的,该问题已提出,根本无法回答。因此,该规则已在Java 9中删除,并且像弱引用和软引用一样在排队时清除了幻像引用。这是不假设对象仍在内存中的更强理由,因为现在即使非优化环境也可以在此时回收对象的内存。