You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重写finalize方法后PhantomReference未入引用队列的原因探究

为什么重写finalize后虚引用未进入ReferenceQueue?

这是个非常典型的Java引用机制与finalize交互的问题,咱们把底层逻辑拆解开来说清楚:

核心逻辑:虚引用入队的前提

PhantomReference(虚引用)是Java四种引用里最弱的一种,它的唯一作用就是在**对象被彻底回收(内存完全释放)**的时候,把自己加入到绑定的ReferenceQueue中。注意这里的关键是「彻底回收」——不是对象第一次被标记为不可达,而是它的内存真正被GC释放了。

finalize方法的“干扰”

当你给类A重写了finalize()方法,并且这个方法还没被执行过的时候,GC的回收流程会发生变化:

  1. 第一次GC发现aObject不可达,但因为它有未执行的finalize()方法,GC不会直接回收它的内存,而是把这个对象放到一个专门的「Finalizer队列」里,等待JVM的Finalizer线程来执行它的finalize()方法。
  2. 在Finalizer线程执行finalize()的过程中,这个对象其实是暂时可达的(因为Finalizer线程持有它的引用),所以此时它还不属于“彻底回收”的状态,对应的虚引用自然不会被加入ReferenceQueue。
  3. 只有当finalize()方法执行完毕后,这个对象才会再次变成不可达,这时候需要第二次GC才能真正回收它的内存,此时虚引用才会被加入队列。

你提到“finalize中没做复活操作”,但其实只要重写了finalize且没被执行过,JVM就会自动触发这个“暂时复活”的流程——这是finalize机制的固有设计,目的是给对象一次自救的机会,哪怕你没在方法里写任何复活代码。

验证一下:触发第二次GC试试

如果修改你的代码,在第一次GC和sleep之后,再触发一次GC,你就能看到虚引用进入队列了:

public class Test { 
    public static void main(String[] args) throws Exception { 
        A aObject = new A(); 
        ReferenceQueue<A> queue = new ReferenceQueue<>(); 
        PhantomReference<A> phantomRef = new PhantomReference<>(aObject, queue); 
        aObject = null; 
        System.gc(); 
        TimeUnit.SECONDS.sleep(1); // 等待Finalizer线程执行完finalize
        System.gc(); // 第二次GC,彻底回收对象
        TimeUnit.SECONDS.sleep(1);
        System.out.println(queue.poll()); 
    } 
} 
class A{ 
    @Override 
    protected void finalize() throws Throwable { 
        super.finalize(); 
        System.out.println("finalize"); 
    } 
}

执行结果会变成:

finalize
java.lang.ref.PhantomReference@xxxxxx

无finalize方法的情况

当你删掉A类的finalize()方法后,GC发现aObject不可达时,会直接回收它的内存,不需要经过Finalizer队列的流程,所以虚引用会立刻被加入ReferenceQueue,这就是你看到queue.poll()返回引用实例的原因。


内容的提问来源于stack exchange,提问作者gesanri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:24:22