Reference
Reference是所有引用类型的父类,它与垃圾回收器合作来来进行GC,Reference类定义了子类的主要逻辑,所以在SoftReference、WeakReference和PhantomReference中几乎完全复用了Reference的逻辑。
下面进入Reference类中进行分析它的原理。
构造函数和属性
其中一个构造函数可以传入一个队列,如果不传入那么使用默认的队列。如果使用第一种方式的话,可以方便的通过队列的引用来判断对象是否被回收。
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
ReferenceQueue队列实现了的入队(enqueue)和出队(poll还有remove)操作,内部元素就是泛型的Reference,通过Reference对象自身的链表结构所实现的。这一点后面再说。先看Reference的属性。
// 保存reference指向的对象。
private T referent;
volatile ReferenceQueue<? super T> queue;
Reference next;
transient private Reference<T> discovered;
private static Reference<Object> pending = null;
- queue:引用对象关联的引用队列。是对象即将被回收时所要通知的队列。当对象将被回收时,reference对象( 而不是referent引用的对象 )会被放到queue里面,然后外部程序即可通过监控这个queue拿到相应的数据了。
名义上是一个队列,实际内部是使用单链表来表示的单向队列,可以理解为queue就是一个链表,其自身仅存储当前的head节点,后面的节点由每个reference节点通过next来保持即可。 - next:指向下一个引用,Reference是一个单链表的结构。
- pending 等待添加到queue中的元素链表。注意这是一个静态对象,意味着所有Reference对象共用同一个pending链表。
- discovered:表示要处理的对象的下一个对象。pending链表使用discovered来查找下一个reference,而ReferenceQueue则用next。
总结一下:
- 源码中没有给pending和discovered初始赋值的操作,那么必定是VM的操作。
- 有两个链表,分别是Reference Queue 和 pending list,两个链表的元素都是Reference的实例,分别通过next和discovered连接起来。前者的头元素在ReferenceQueue中,后者的头元素就是pending。
Reference对象的生命周期
在了解生命周期之前,我们先看一下pending list的处理线程,前面说到 VM 会自动的添加Reference对象到pending list中,另外一个单线程将pending list中的元素移到Reference Queue中。这个线程的定义以及启动在Reference类中。
private static class ReferenceHandler extends Thread {
@Override
public void run() {
while (true) {
tryHandlePending(true);
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;// 拿到pending链表的头元素
c = r instanceof Cleaner ? (Cleaner) r : null;
pending = r.discovered;//删除头元素,移向一下个Reference
r.discovered = null;
} else {
if (waitForNotify) {
lock.wait();// notify唤醒操作在VM中
}
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
Thread.yield();
return true;
} catch (InterruptedException x) {
return true;
}
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
}
pending是由jvm来赋值的,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。ReferenceHandler线程要做的是将pending对象enqueue,但默认我们所提供的queue,也就是从构造函数传入的是null,实际是使用了ReferenceQueue.NULL,Handler线程判断queue为ReferenceQueue.NULL则不进行操作,只有非ReferenceQueue.NULL的queue才会将Reference进行enqueue。
结合代码已经源码的中的大幅注释, Reference的生命周期如下图 :
Reference实例一共有四种状态,Active(活跃状态)、Pending(半死不活状态)、Enqueued(濒死状态)、Inactive(死亡状态),
- Active:
reference如果处于此状态,会受到垃圾处理器的特殊处理。当垃圾回收器检测到referent已经更改为合适的状态后(没有任何强引用和软引用关联) - Pending:
实例如果处于此状态,表明它是pending-Reference列表中的一个元素,等待被Reference-handler线程做入队处理。 - Enqueued:
实例如果处于此状态,表明它已经是它注册的引用队列中的一个元素,当它被从引用队列中移除时,它的状态将会变为Inactive,未注册引用队列的实例永远不会处于该状态。 - Inactive:
实例如果处于此状态,它的状态将永远不会再改变了。
JVM并没有明确的定义状态,而是通过现有的字段值来体现Reference的状态。
- next==null,则reference处于Active状态;
- next!=null && queue == ReferenceQueue.NULL, 则reference处于Inactive状态;
- next!=null && queue == ReferenceQueue.ENQUEUED,则reference处于Enqueue状态;
- discovered!=null 则reference处于Pending状态。
小结
- Reference框架是作为 JVM GC于语言层面的一个消息传递方式,使我们可以对对象被回收时做一些处理。比如WeakHashMap DirectByteBuffer正是利用此机制来实现。
- 构造Reference及其子类时,若传入了ReferenceQueue需要及时Poll,否则Reference对象不能得到释放
- 本文大体的介绍了Reference框架,没有描述其子类的具体流程,后面会逐个介绍。
- 如果在创建Reference时没有传入Queue,那么Reference对象也不会进入Pedding list,从hander方法中也可以看出。