重写finalize方法后PhantomReference未入引用队列的原因探究
为什么重写finalize后虚引用未进入ReferenceQueue?
这是个非常典型的Java引用机制与finalize交互的问题,咱们把底层逻辑拆解开来说清楚:
核心逻辑:虚引用入队的前提
PhantomReference(虚引用)是Java四种引用里最弱的一种,它的唯一作用就是在**对象被彻底回收(内存完全释放)**的时候,把自己加入到绑定的ReferenceQueue中。注意这里的关键是「彻底回收」——不是对象第一次被标记为不可达,而是它的内存真正被GC释放了。
finalize方法的“干扰”
当你给类A重写了finalize()方法,并且这个方法还没被执行过的时候,GC的回收流程会发生变化:
- 第一次GC发现
aObject不可达,但因为它有未执行的finalize()方法,GC不会直接回收它的内存,而是把这个对象放到一个专门的「Finalizer队列」里,等待JVM的Finalizer线程来执行它的finalize()方法。 - 在Finalizer线程执行
finalize()的过程中,这个对象其实是暂时可达的(因为Finalizer线程持有它的引用),所以此时它还不属于“彻底回收”的状态,对应的虚引用自然不会被加入ReferenceQueue。 - 只有当
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
相关产品推荐
相关产品推荐

