Reference 类
对象的引用句柄,主要是负责内存的一个状态,当然它还和 java 虚拟机,垃圾回收器打交道。
referent 指代 reference 引用的对象,如果 referent 已经被程序或者垃圾回收器清理,则为 null。它直接被 GC 程序处理。
private T referent; /* Treated specially by GC */
引用实例处于四种可能的内部状态之一:
Active:
reference 如果处于此状态,会受到垃圾处理器的特殊处理。当垃圾回收器检测到 referent 已经更改为合适的状态后(没有任何强引用和软引用关联),会在某个时间将实例的状态更改为 Pending 或者 Inactive。具体取决于实例是否在创建时注册到一个引用队列中。在前一种情况下(将状态更改为 Pending),他还会将实例添加到 pending-Reference 列表中。新创建的实例处于活动状态。
Pending:
实例如果处于此状态,表明它是 pending-Reference 列表中的一个元素,等待被 Reference-handler 线程做入队处理。未注册引用队列的实例永远不会处于该状态。
Enqueued:
实例如果处于此状态,表明它已经是它注册的引用队列中的一个元素,当它被从引用队列中移除时,它的状态将会变为 Inactive,未注册引用队列的实例永远不会处于该状态。
Inactive:
实例如果处于此状态,那么它就是个废实例了(滑稽),它的状态将永远不会再改变了。
所以实例一共有四种状态,Active(活跃状态)、Pending(准备回收状态)、Enqueued(已回收状态)、Inactive(最终状态)。当然,Pending和Enqueued状态是引用实例在创建时注册了引用队列才会有。
volatile ReferenceQueue<? super T> queue;
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
@SuppressWarnings("rawtypes")
volatile Reference next;
实例处于不同的状态,queue、next 变量对应不同的值,JVM中并没有显示定义这样的状态,而是通过next和queue来进行判断。
Active:如果创建 Reference 对象时,没有传入 ReferenceQueue,queue=ReferenceQueue.NULL。如果有传入,则 queue 指向传入的 ReferenceQueue 队列对象。next == null;
Pending:queue 为初始化时传入 ReferenceQueue 对象;next == this;
Enqueue:queue == ReferenceQueue.ENQUEUED;next 为 queue 中下一个 reference 对象,或者若为最后一个了 next == this;
Inactive:queue == ReferenceQueue.NULL; next == this.
/* When active: next element in a discovered reference list maintained by GC (or this if last)
* pending: next element in the pending list (or null if last)
* otherwise: NULL
*/
transient private Reference<T> discovered; /* used by VM */
discovered 表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过 discovered 进行排队,这边只需要不停地拿到 pending,然后再通过 discovered 不断地拿到下一个对象赋值给 pending 即可,直到取到了最有一个。它是被 JVM 使用的。
锁对象用于与垃圾收集器同步。收集器必须在每个收集周期的开始获得此锁。用作在操作 pending 链表时的同步对象。
static private class Lock { }
private static Lock lock = new Lock();
用来保存那些需要被放入队列中的 reference,收集器会把引用添加到这个列表里来,Reference-handler 线程会从中移除它们。这个列表由上面的 lock 对象锁进行保护。列表使用 discovered 字段来链接它的元素。
private static Reference<Object> pending = null;
pending:等待添加到 queue 中的元素链表。注意这是一个静态对象,意味着所有 Reference 对象共用同一个 pending 队列。
ReferenceQueue queue = new ReferenceQueue();
// 创建弱引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null
WeakReference wf1 = new WeakReference(new Object(), queue);
WeakReference wf2 = new WeakReference(new Object(), queue);
WeakReference wf3 = new WeakReference(new Object(), queue);
//手动触发回收
System.gc();
- 准备回收的 reference 对象会被 JVM 存放到 pending 链表,内部会启动一个 ReferenceHandler 线程把 pending 链表元素存放到引用队列中。
- 而 从上图中发现 discovered 链表存放的是下一个需要回收的对象链表。
由此可知,加入到回收的动作流程:垃圾回收器会把 References 添加进入,Reference-handler thread 会移除它。
ReferenceHandler线程
Reference 类中有一个特殊的线程叫 ReferenceHandler,专门处理那些 pending 链表中的引用对象。ReferenceHandler 类是 Reference 类的一个静态内部类,继承自 Thread,所以这条线程就叫它 ReferenceHandler 线程。
private static class ReferenceHandler extends Thread {
private static void ensureClassInitialized(Class<?> clazz) {
//预先加载指定Class实例
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}
static {
// 预加载并初始化 InterruptedException 和 Cleaner 类
// 来避免出现在循环运行过程中时由于内存不足而无法加载它们
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
使用一个死循环来对 pending 链表执行操作:
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 使用 'instanceof' 有时会导致OOM
// 所以在将r从链表中摘除时先进行这个操作
c = r instanceof Cleaner ? (Cleaner) r : null;
// 移除头结点,将pending指向其后一个节点
pending = r.discovered;
//从链表中移除
r.discovered = null;
} else {
// 在锁上等待可能会造成OOM,因为它会试图分配exception对象
if (waitForNotify) {
// 导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过
lock.wait();
}
// 重试
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
Thread.yield();
// 重试
return true;
} catch (InterruptedException x) {
// 重试
return true;
}
// 如果移除的元素是Cleaner类型,则执行其clean方法
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
//对Pending状态的实例入队操作
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
在根线程组中启动了一条最高优先级的 ReferenceHandler 线程,并覆盖了 JVM 中对 pending 的默认处理方式。
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
// 将handler线程注册到根线程组中并设置最高优先级
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
// 覆盖jvm的默认处理方式
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
ReferenceQueue
引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到队列中,ReferenceQueue 实现了入队(enqueue)和出队(poll),还有 remove 操作,内部元素 head 就是泛型的 Reference。
static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();
static private class Lock { };
private Lock lock = new Lock();
private volatile Reference<? extends T> head = null;
private long queueLength = 0;
- 使用 ENQUEUED -> Enqueue、NULL -> Inactive 表示对应的状态
- 使用 head 链表保存 Inactive 状态的 Reference 对象
从上图可以看出,最终指定的引用队列的 head 链表存放了最终状态的 Reference 对象。
接下来看下引入队列的入队操作:
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// queue 为 null 或者 queue 已经被回收了,直接返回
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// 将queue设置为Enqueued,表示已经被回收
r.queue = ENQUEUED;
// 判断当前链表是否为null,不为null,r.next指向head,为null则指向自己
r.next = (head == null) ? r : head;
// head指针指向r
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
从上面的源码分析可以看出:JVM 负责设置可回收的对象引用 Reference 的内部状态及断开与被引用对象的关系,而 Reference 内部线程会不断轮询处理可回收的引用 Reference,把可回收的引用存放到引用队列,接着 JVM 垃圾回收机制回收引用队列的元素。
整体流程:
参考文章:
java 源码系列 - 带你读懂 Reference 和 ReferenceQueue
你不可不知的Java引用类型之——Reference源码解析