我们知道 Java
扩充了“引用”的概念,引入了软引用、弱引用和虚引用,它们都属于 Reference
类型,也都可以配合 ReferenceQueue
使用。你是否好奇常常被一笔带过的“引用对象
的处理过程”?你是否在探究 NIO
堆外内存的自动释放时看到了 Cleaner
的关键代码但不太能梳理整个过程?你是否好奇在研究 JVM
时偶尔看到的 Reference Handler
线程?本文将分析 Reference
和 ReferenceQueue
的源码带你理解引用对象
的工作机制。
事实上,个人感觉在无相关前置知识的情况下,单纯看 JDK
的 Java
代码是没办法很好地理解引用对象
是如何被添加到引用队列
中的。因为 Reference
的 pending
字段的含义和赋值操作是隐藏在 JVM
的 C++
代码中,本文搁置了其中的细节,仅分析 JDK
中相关的 Java
代码。
Reference
Reference
是引用对象
的抽象基类。此类定义了所有引用对象通用的操作。由于引用对象是与垃圾收集器密切合作实现的,因此该类可能无法直接子类化。
构造函数
referent
: 引用对象
关联的对象
queue
: 引用对象
准备注册到的引用队列
Reference
提供了两个构造函数,一个需要传入引用队列
(ReferenceQueue
),一个不需要。如果一个引用对象
(Reference
)注册到一个引用队列
,在检测到关联对象有适当的可达性变化后,垃圾收集器将把该引用对象
添加到该引用队列。
“关联对象有适当的可达性变化”并不容易理解,在很多表述中它很容易被简化为“可以被回收”,但是同时我们又拥有另一条规则,即“一个对象是否可回收的判断依据是是否从 Root
对象可达”。在面对 Reference
的子类时,我们有种割裂感,好像一条和谐的规则出现了特殊条例。探索 Java 类 Cleaner 和 Finalizer
1 2 3 4 5 6 7 8 9
| Reference(T referent) { this(referent, null); }
Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
|
属性
成员变量
referent
: 引用对象
关联的对象,该对象将被垃圾收集器特殊对待。我们很难直观地感受何谓“被垃圾收集器特殊对待”,它对应着“在检测到关联对象有适当的可达性变化后,垃圾收集器将把引用对象
添加到该引用队列”。
queue
: 引用对象
注册到的引用队列
next
: 用于指向下一个引用对象
,当引用对象
已经添加到引用队列
中,next
指向引用队列
中的下一个引用对象
discovered
: 用于指向下一个引用对象
,用于在全局的 pending
链表中,指向下一个待添加到引用队列
的引用对象
静态变量
注意:lock
和 pending
是全局共享的。
lock
: 用于与垃圾收集器同步的对象,垃圾收集器必须在每个收集周期开始时获取此锁。因此至关重要的是持有此锁的任何代码必须尽快运行完,不分配新对象并避免调用用户代码。
pending
: 等待加入引用队列
的引用对象
链表。垃圾收集器将引用对象
添加到 pending
链表中,而 Reference-Handler
线程将删除它们,并做清理或入队操作。pending
链表受上述 lock
对象的保护,并使用 discovered
字段来链接下一个元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public abstract class Reference<T> { private T referent;
volatile ReferenceQueue<? super T> queue; @SuppressWarnings("rawtypes") volatile Reference next;
transient private Reference<T> discovered;
static private class Lock { } private static Lock lock = new Lock();
private static Reference<Object> pending = null; }
|
Reference
其实可以理解为单链表中的一个节点,除了核心的 referent
和 queue
,next
和 discovered
都用于指向下一个引用对象
,只是分别用于两条不同的单链表上。
pending
链表:
ReferenceQueue
:
ReferenceHandler 线程
启动任意一个非常简单的 Java
程序,通过 JVM
相关的工具,比如 JConsole
,你都能看到一个名为 Reference Handler
的线程。
ReferenceHandler
类本身的代码并不复杂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| private static class ReferenceHandler extends Thread { private static void ensureClassInitialized(Class<?> clazz) { try { Class.forName(clazz.getName(), true, clazz.getClassLoader()); } catch (ClassNotFoundException e) { throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e); } }
static { ensureClassInitialized(InterruptedException.class); ensureClassInitialized(Cleaner.class); }
ReferenceHandler(ThreadGroup g, String name) { super(g, name); }
public void run() { while (true) { tryHandlePending(true); } } }
|
创建线程并启动
Reference-Handler
线程是通过静态代码块创建并启动的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); handler.start();
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override public boolean tryHandlePendingReference() { return tryHandlePending(false); } }); }
|
run 处理逻辑
run
方法的核心处理逻辑。本质上,ReferenceHandler
线程将 pending
链表上的引用对象
分发到各自注册的引用队列
中。如果理解了 Reference
作为单链表节点的一面,这部分代码不难理解,反而是其中应对 OOME
的处理很值得关注,但更多的可能是看了个寂寞,不好重现问题并验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| static boolean tryHandlePending(boolean waitForNotify) { Reference<Object> r; Cleaner c; try { synchronized (lock) { if (pending != null) { r = pending; c = r instanceof Cleaner ? (Cleaner) r : null; pending = r.discovered; r.discovered = null; } else { if (waitForNotify) { lock.wait(); } 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; }
|
关联对象和队列相关方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public T get() { return this.referent; }
public void clear() { this.referent = null; }
public boolean isEnqueued() { return (this.queue == ReferenceQueue.ENQUEUED); }
public boolean enqueue() { return this.queue.enqueue(this); }
|
ReferenceQueue
引用队列
,在检测到适当的可达性更改后,垃圾收集器将已注册的引用对象
添加到该队列。
属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class ReferenceQueue<T> {
public ReferenceQueue() { } private static class Null<S> extends ReferenceQueue<S> { boolean enqueue(Reference<? extends S> r) { return false; } } 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; }
|
入队
enqueue
只能由 Reference
类调用。
引用对象
的 queue
字段可以表达引用对象
的状态:
NULL
:表示没有注册到引用队列
或者已经从引用队列
中移除
ENQUEUED
:表示已经添加到引用队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| boolean enqueue(Reference<? extends T> r) { synchronized (lock) { ReferenceQueue<?> queue = r.queue; if ((queue == NULL) || (queue == ENQUEUED)) { return false; } assert queue == this; r.queue = ENQUEUED; r.next = (head == null) ? r : head; head = r; queueLength++; if (r instanceof FinalReference) { sun.misc.VM.addFinalRefCount(1); } lock.notifyAll(); return true; } }
|
出队
轮询队列以查看是否有引用对象可用,如果存在可用的引用对象则将其从队列中删除并返回,否则该方法立即返回 null
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public Reference<? extends T> poll() { if (head == null) return null; synchronized (lock) { return reallyPoll(); } }
private Reference<? extends T> reallyPoll() { Reference<? extends T> r = head; if (r != null) { @SuppressWarnings("unchecked") Reference<? extends T> rn = r.next; head = (rn == r) ? null : rn; r.queue = NULL; r.next = r; queueLength--; if (r instanceof FinalReference) { sun.misc.VM.addFinalRefCount(-1); } return r; } return null; }
|
出队操作提供了等待的选项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public Reference<? extends T> remove() throws InterruptedException { return remove(0); }
public Reference<? extends T> remove(long timeout) throws IllegalArgumentException, InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("Negative timeout value"); } synchronized (lock) { Reference<? extends T> r = reallyPoll(); if (r != null) return r; long start = (timeout == 0) ? 0 : System.nanoTime(); for (;;) { lock.wait(timeout); r = reallyPoll(); if (r != null) return r; if (timeout != 0) { long end = System.nanoTime(); timeout -= (end - start) / 1000_000; if (timeout <= 0) return null; start = end; } } } }
|
状态变化
Reference
实例(引用对象)可能处于四种内部状态之一:
Active
: 新创建的实例处于 Active
状态,受到垃圾收集器的特殊处理。收集器在检测到关联对象
的可达性变为适当状态后的一段时间,会将实例的状态更改为 Pending
或 Inactive
,具体取决于实例在创建时是否注册到引用队列
中。在前一种情况下,它还会将实例添加到待 pending-Reference
列表中。
Pending
: 实例处在 pending-Reference
列表中,等待 Reference-Handler
线程将其加入引用队列
。未注册到引用队列
的实例永远不会处于这种状态。
Enqueued
: 处在创建实例时注册到的引用队列
中。当实例从引用队列中删除时,该实例将变为 Inactive
状态。未注册到引用队列
的实例永远不会处于这种状态。
Inactive
: 没有进一步的操作。一旦实例变为 Inactive
状态,其状态将永远不会再改变。
Reference
实例(引用对象)的状态由 queue
和 next
字段共同表达:
Active
: (queue == ReferenceQueue || queue == ReferenceQueue.NULL) && next == null
Pending
: queue == ReferenceQueue && next == this
Enqueued
: queue == ReferenceQueue.ENQUEUED && (next == Following || this)
(在队列末尾时,next
指向自身,目前没有体现出这么设计的必要性啊?)
Inactive
: queue == ReferenceQueue.NULL && next == this
Reference 的子类
参考文章