Cleaner:Java的一种新资源释放方式

54 阅读2分钟

引言

在JDK9中新增了Cleaner类,该类的作用是用于替代finalize方法,更有效地释放资源并避免内存泄漏。

基本概念和使用

JEP260提案中,封装了大部分Sun包内部的API之余,还引入了一些新的API,其中就包含着Cleaner这个工具类。
Cleaner承担着替换finalize方法的作用,为了解决finalize方法的性能问题、安全问题以及不可靠。

Cleaner 类的主要方法和属性

Cleaner类中,一共就三个外部方法,一个简单的工具类。
output.png
其中,在调用create方法时,就会新建一条线程,用于监听目标对象是否已经被回收。监听的逻辑则是用到了虚引用以及引用队列,在虚引用中,要是一个对象变成不可达后,在GC前会将该对象的虚引用放入引用队列中。详细的步骤以及逻辑可以看这篇文章【Java引用规范】虚引用以及引用队列

如何创建和使用 Cleaner 对象

  1. 使用Cleaner.create()创建Cleaner对象。
  2. 调用cleaner.register()方法,传入监听的对象以及回收后要执行的逻辑。其中,逻辑中不能带有监听对象的引用,否则对象将永远无法被回收。

实际应用和示例

典型的 Cleaner 类使用场景和示例代码

在JDK1.2中,就已经有这个类的内部实现了,不过是在sun包中实现的。由于是内部类不建议在生产代码中直接使用。不过sun包下的Cleaner类和lang包下的Cleanr类的功能是类似的。
Cleaner在JDK中最典型的实现就是堆外内存的回收。我们申请到一个堆外内存后,是无法手动将该堆外内存进行显式回收的,只能等待JVM来自动回收该内存。
其中,自动回收的操作就是使用到了Cleaner工具类,在DirectByteBuffer的构造方法中,申请到堆外内存后,就会将堆外内存地址、申请容量以及实际内存大小传入到Deallocator类中进行空间的回收。
output.png
Deallocator类集成了Runnable接口,在run方法中就会将对应地址的堆外内存回收。
output.png

优点和局限性

Cleaner 类相比 Finalizer 和 PhantomReference 的优势

类型自动化清理易用性安全性性能影响
Cleaner开箱即用更强较小
Finalizer开箱即用较弱较大
PhantomReference需要与引用队列相结合,额外代码工作量较弱较小

Cleaner 类和手动调用Close方法的区别

操作执行时机确定性是否自动化清理资源泄漏风险
Cleaner对象被回收时隐式调用执行时机无法控制低(只要对象不再可达,就会自动清理)
调用Close方法代码显示调用只要调用,一定执行高(如果忘记调用 close 方法,可能会导致资源泄露)

Cleaner 类的潜在问题和限制

  1. 每注册一个Cleaner类,就会新开一条线程用于监听目标对象是否已经进入到引用队列。直到目标对象被回收后,新线程才结束。
  2. Cleaner回收时间点无法控制。
  3. 不能替换所有的资源释放,必要时还是需要显式执行Close方法。
  4. 无法控制传入的回收执行逻辑,可能导致性能问题。