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

为何Java中实现非空finalize()方法会导致Cleaner无法运行?

为何Java中实现非空finalize()方法会导致Cleaner无法运行?

这个问题其实和Java垃圾回收器的处理逻辑以及finalize()方法的特殊机制直接相关,我来给你一步步捋清楚:

首先得明确两个核心组件的工作方式:

  • Cleaner:它基于虚引用(PhantomReference)和引用队列实现。当被注册的对象完全不可达时,GC会把对应的虚引用推入引用队列,Cleaner的后台线程会监控这个队列,一旦发现引用就执行你注册的清理动作。
  • finalize()方法:如果一个类重写了非空的finalize(),那么当对象第一次被GC判定为不可达时,JVM不会直接回收它,而是把它标记为"需要执行finalize",并放到专门的Finalizer队列里,由单独的Finalizer线程来执行finalize()方法。只有当finalize()执行完成后,这个对象才会在下一次GC中真正变成可回收状态,此时才会触发Cleaner的清理逻辑。

回到你的例子:
当finalize()是空方法时,JVM会做优化——直接跳过这个方法的执行,对象可以被直接回收,所以Cleaner的动作能立刻被触发,你的断言就通过了。

但一旦finalize()里有实际代码(哪怕只是一行打印),JVM必须执行这个方法。此时你的测试代码只调用了一次System.gc(),然后休眠1秒:

  1. 第一次GC把对象放到Finalizer队列,等待Finalizer线程执行finalize()
  2. 可能Finalizer线程还没来得及执行完(或者执行完了但没触发第二次GC),你的断言就已经执行了,此时Cleaner的动作还没被触发,所以断言失败。

你可以做个小测试验证这个逻辑:在Thread.sleep(1000)之后再调用一次System.gc(),或者延长休眠时间到3-5秒,你会发现cleaned.get()最终会变成1——因为Finalizer线程执行完finalize()后,第二次GC会真正回收对象,触发Cleaner的清理动作。

另外还要提醒一句:Java 9之后官方就不推荐使用finalize()了,它的执行时机完全不可控,还会拖慢GC效率。你的兼容方案(反射加载Cleaner+fallback到finalize)是合理的,但要注意当finalize()存在时,Cleaner的执行会被延迟到finalize()完成后的下一次GC。

你的精简示例代码:

public abstract class Cleanable {
    private static final Cleaner cleaner = Cleaner.create();
    private final Cleaner.Cleanable cleanable;
    private Runnable cleanAction;

    public Cleanable(Runnable inputCleanAction) {
        cleanable = cleaner.register(this, inputCleanAction);
        this.cleanAction = inputCleanAction;
    }

    @Override
    public void finalize() throws Throwable {
        System.out.println("finalize called");
    }
}

测试代码:

AtomicInteger cleaned = new AtomicInteger(0);
{
    Cleanable cleanable = new Cleanable(() -> cleaned.incrementAndGet()) {};
    cleanable = null;
}
System.gc();
Thread.sleep(1000);

Assert.assertEquals(1, cleaned.get());

备注:内容来源于stack exchange,提问作者bbnt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 10:53:00