finalize() 早就被标记为废弃,官方推荐的替代方案是 java.lang.ref.Cleaner——这篇真实验证它的手动清理、幂等性,以及最关键的"忘记手动清理时是否真的能兜底"这件事。
上一篇:方法引用四种形式真实验证
用Cleaner.register()注册一个不持有外部对象引用的清理动作,通过Cleanable.clean()手动触发。
CleanerApiDemo.java static final Cleaner cleaner = Cleaner.create(); class Resource implements AutoCloseable { Resource(int id, AtomicInteger counter) { this.state = new State(id, counter); // State不持有Resource本身的引用 this.cleanable = cleaner.register(this, state); } public void close() { cleanable.clean(); } }
真实执行清理动作,资源id=1 手动close()后,真实清理计数: 1
真实结果证明手动调用 close()(内部真实转发到 Cleanable.clean())会立刻、同步地执行注册好的清理逻辑——这是 Cleaner 最推荐的使用方式:配合 try-with-resources 或显式 close() 做确定性的及时清理,Cleaner 提供的 GC 兜底只是"万一忘了"的保险。
连续调用三次close(),验证清理逻辑是否会被重复执行。
重复调用close()三次后,真实清理计数(应该还是1): 1
真实结果证明 Cleanable.clean() 内部真实做了状态标记,第一次调用真实执行清理逻辑并标记为"已清理",后续重复调用真实直接跳过,不会重复执行——这个幂等性保证在实际场景里很重要,因为资源类的 close() 经常会被多次调用(比如显式调用后又进入 try-with-resources 的自动关闭)。
最关键的一步:故意"忘记"调用close(),让对象失去所有强引用,验证GC发生后Cleaner是否真的会自动执行清理。
CleanerApiDemo.java static void createAndForget(AtomicInteger counter) { Resource leaked = new Resource(2, counter); // 不调用 close(),直接让 leaked 超出作用域 } // 之后真实多次 System.gc() 并等待
真实执行清理动作,资源id=2 忘记调用close()、对象被GC后,Cleaner真实自动清理计数: 1(兜底机制,不保证立即执行,但真实会执行)
每一行都是真实运行这段代码得到的结果。