需要提前说明的是,本文讲述的是JDK8中位于sun.misc包下的Cleaner(清洁工)类(高版本中被迁移至jdk.internal.ref包)。而从JDK9开始,Java在java.lang.ref包下实现了另一个清洁工类,且该类并非PhantomReference(虚引用)类的子类。虽说两者作用高度相似(应该是对旧功能的新实现),但实际上两者都被保留下来并各自发挥着作用,因此请注意区别两者。由于新清洁工类自身携带有一套完整运行流程的缘故,因此目前的主流说法中都将JDK9版本的清洁工类的运行流程称之为清洁工机制,故本文中不会出现清洁工机制这个称呼。
清洁工类是专门为了替代Finalization(终结)机制/finalize()方法而实现的,学习者很容易冒出这个想法…该想法是否正确暂且不论,但清洁工类确实实现了与终结机制/finalize()方法相同的功能,即在所指对象被GC回收时执行自定义操作。与常规注册了引用队列的Reference(引用)抽象类对象不同,拿WeakReference(弱引用)类对象举例,如果我们想在其所指对象被GC回收时执行一些操作,首先需要等待引用机制将弱引用置入引用队列中,随后再将之从中取出后或执行弱引用的isEnqueued()方法,因为我们需要通过该操作来判断所指对象是否已/会被GC回收。换句话说就是我们需要先手动的判断所指对象是否已/会被GC回收再去执行自定义操作…这就增加了我们编码的复杂性,但如果使用清洁工的话我们就不需要再做这一步了。
清洁工类继承自虚引用类,这意味着其本身也是一个虚引用。当清洁工的所指对象被GC回收时,按照引用机制的统一流程,其会被置入引用队列中。但之前在引用抽象类的文章中已经特意提及过,引用机制在将引用加入引用队列前存在一个特殊判断,即如果引用机制发现引用是一个清洁工,则会执行其内部包含的自定义操作。这意味着我们无需再做手动的判断,甚至于自定义操作都不会发生在用户线程中,引用机制会直接在后台自动处理执行自定义逻辑,从而简化了开发者编码。
事实上上述做法是不规范的,虽然这看起来只是单纯的特殊判断,但本质却是上级耦合了下级,在父类中对子类做特殊处理并不合常规…因此会有清洁工类是专门为了替代终结机制/finalize()方法而实现的这种想法也就理所当然了。除此之外还有一点也能应征这个想法,即清洁工在执行完自定义操作后会放弃加入引用队列,并直接退出流程开始对下个引用做处理…连引用队列都不入了…说它不是专门为了取代终结机制/finalize()方法谁信呐?
/**
* @Description: 清洁工类,直接继承与虚引用类
*/
public class Cleaner extends PhantomReference<Object> {
...
}
dummyQueue —— 假队列 —— 用于填充创建虚引用时必要的引用队列参数,因此所有清洁工的注册引用队列都相同,该参数没有实际作用。
/**
* @Description: 假引用队列(引用机制是不会把清洁工对象加入引用队列的,
* @Description: 因此这个引用队列除了填充虚引用类的构造方法的参数外实际上没有任何作用)
*/
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
first —— 头 —— 持有清洁工链表头清洁工的引用。
/**
* @Description: 头清洁工(清洁工链表的头清洁工)
*/
private static Cleaner first = null;
next —— 下个 —— 持有当前清洁工后继清洁工的引用。
/**
* @Description: 后继清洁工
*/
private Cleaner next = null;
prev —— 上个 —— 持有当前清洁工前驱清洁工的引用。
/**
* @Description: 前驱清洁工
*/
private Cleaner prev = null;
thunk —— 替换 —— 保存当前清洁工需要执行的可运行/任务。
/**
* @Description: 形态转换(自定义操作)
*/
private final Runnable thunk;
private Cleaner(Object var1, Runnable var2) —— 通过所指对象及可运行/任务创建清洁工。
private Cleaner(Object var1, Runnable var2) {
// 调用虚引用类的构造方法。清洁工的本质是一个虚引用类对象,因此必然存在一个所指对象。
super(var1, dummyQueue);
// 设置自定义操作。
this.thunk = var2;
}
private static synchronized Cleaner add(Cleaner var0) —— 新增 —— 向清洁工链表的头部插入指定清洁工。
/**
* @Description: 新增指定清洁工
*/
private static synchronized Cleaner add(Cleaner var0) {
// 如果头清洁工不为null,则将头清洁工设置为新清洁工的后继清洁工,将头清洁工的前驱清洁工设置为新清洁工。
if (first != null) {
var0.next = first;
first.prev = var0;
}
// 设置新清洁工为头清洁工。由此可知清洁工链表双向链表,且采用的是头插法。
first = var0;
return var0;
}
private static synchronized boolean remove(Cleaner var0) —— 移除 —— 将指定清洁工从清洁工链表中移除。
/**
* @Description: 移除指定清洁工
*/
private static synchronized boolean remove(Cleaner var0) {
if (var0.next == var0) {
// 如果指定清洁工的后继清洁工使其自身,则表示已从链表中移除,不能再次移除。
return false;
} else {
// 如果指定清洁工是头清洁工。
if (first == var0) {
if (var0.next != null) {
// 如果存在后继清洁工,则将头清洁工赋值为后继清洁工。
first = var0.next;
} else {
// 如果不存在后继清洁工,则将头清洁工赋值为前驱清洁工...其实可以直接设置为null的,因此不存在后续就意味着这是最后一个清洁工了。
first = var0.prev;
}
}
// 节点的正常删除而产生的前后链接操作。
if (var0.next != null) {
var0.next.prev = var0.prev;
}
if (var0.prev != null) {
var0.prev.next = var0.next;
}
// 将指定清洁工的前驱/后继清洁工设置为自身,表示其已经从链表中移除。
var0.next = var0;
var0.prev = var0;
return true;
}
}
public static Cleaner create(Object var0, Runnable var1) —— 创建 —— 通过所指对象及可运行/任务创建清洁工,创建的清洁工默认处于清洁工链表中
/**
* @Description: 创建一个清洁工。
*/
public static Cleaner create(Object var0, Runnable var1) {
// 实例化一个清洁工,并将之插入到清洁工链表中。
return var1 == null ? null : add(new Cleaner(var0, var1));
}
public void clean() —— 清理 —— 执行可运行/任务。
/**
* @Description: 清理
*/
public void clean() {
// 将当前的清洁工类对象从清洁工链表中移除,表示其开始/正在/结束了清理。
if (remove(this)) {
try {
// 执行自定义操作。
this.thunk.run();
} catch (final Throwable var2) {
// 访问控制器执行特权。
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
// 错误输出流。
if (System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
}
// 终止虚拟机,即如果当前方法不能正常执行,则必须结束整个程序,说明当前方法时保证程序运行的底层方法。
System.exit(1);
return null;
}
});
}
}
}