java在最开始设计的时候一个对象只存在被引用和没有被引用两种状态,如此设计在概念上会比较清晰,且垃圾回收的判断与实现也会比较简单。但是随着应用场景的增加,实际上,我们更希望存在这样的一类对象:当有足够的内存时,这些对象能够继续存活;而当内存空间不足需要进行垃圾回收,或者在进行了垃圾回收之后空间还是非常紧张,则可以抛弃这些对象。这种特性,可以在很多场景下发挥作用,例如缓存功能、对象存活周期监控、堆外内存释放等等。
在JDK1.2之后对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种类型的引用,通过与JVM GC的紧密协作赋予各种引用以不同的垃圾收集行为。接着又基于较弱的引用(Weak,Phantom)不会影响到被引用对象在gc中的生存周期这一特性,扩展并实现了诸如对象析构方法的支持,以及nio DirectByteBuffer被GC回收时自动内存释放等等功能。此外,一些著名的第三方类库也利用这种特性实现了自己定制的对象回收逻辑处理机制。
本篇就是探究一下引用类型的相关源代码实现及其与JVM GC协同工作的机制,并介绍两个开源框架中对Reference的定制使用场景。
在java中,存在着强引用(=),软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)这4种引用类型。这些引用的类型与jvmgarbage collector对对象的回收行为密切相关:如果一个对象强引用可达,就一定不会被GC回收,哪怕jvm抛出OOM异常;而如果一个对象只有软引用可达,则虚机会保证在out of memory之前会回收该对象;如果一个对象只是弱引用或者虚引用可达,则下一次GC时候就会将其回收。弱引用和虚引用的区别是,当一个对象只是弱引用可达时,它在下一次GC来临之前还有抢救的余地,也就是说弱引用仍可以获得该被引用的对象,然后将其赋值给某一个GC Root可达的变量,此时它就“得救”了;而当一个对象只是虚引用可达时候,它已经无法被抢救了,因为虚引用无法得到被引用对象。
另外,Java还提供了ReferenceQueue用于在一个对象被gc回收掉的时候可以进行额外的处理。ReferenceQueue即是这样的一个队列,当一个对象被gc回收之后,其相应的包装类,即Reference对象会被放入队列中。我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。
由上面的说明可知,我们可以使用WeakReference或者PhantomReference来观察某些对象被gc回收的动作,并额外执行某些逻辑,而不会影响到他们的生命周期。事实上,JDK中自己对NIO DirectByteBuffer在GC回收后其申请的直接内存释放功能,以及netty提供的ObjectCleaner对象清理机制,都是通过WeakReference或者PhantomReference的子类来实现的。
Reference及ReferenceQueue相关的使用代码可参照另外一篇文章《使用ReferenceQueue实现对ClassLoader垃圾回收过程的观察、以及由此引发的ClassLoader内存泄露的场景及排查过程》,此处不再赘述。
本文代码分析基于JDK1.8,在JDK9+之后,关于这部分代码会有不小的变动,比如Reference对象的状态发生了变化,对Finalizer析构对象的弃用等,但是万变不离其宗,理解整体的设计实现思想就可以了解JDK及JVM GC是如何合作实现各种不同引用类型对象的垃圾收集及其被回收之前的行为。
Reference是所有引用对象(除了强引用)的抽象类型,其定义了所有引用对象的公共操作。由于Reference引用对象与jvm的gc模块在实现上是紧密合作的,因此并不适合被直接继承并扩展。一个Reference对象会处于4个可能的内部状态之中:
Reference类持有的属性说明:
//该Reference关联的对象,该对象会被gc专门对待
private T referent
//该Reference对象关联的队列
volatile ReferenceQueue queue
//已入自己注册的队列queue中的下一个对象(相同的queue)
volatile Reference next
//供jvm使用,用于将gc找到的active对象或pending list中的对象链接起来
transient private Reference discovered
//用于与gc collector同步使用,gc collector必须获得该lock才能开始collect回收,
//因此持锁以后应尽快做完、不分配新对象并避免调用用户代码
Lock lock
//这个是static类属性,全局唯一的,代表了pending list的链头。gc collector添加Reference对象到此list中,
//同时Reference Handler线程逐个将它们移出并处理(加入到queue中,或者直接执行其clean方法)。
//该链使用上述lock进行保护,并使用Reference对象的discovered属性链接起来
static Reference
一个Reference的状态由next和queue属性共同确定如下:
为了确保一个并发执行的Garbage Collectors能发现活跃的Reference对象,而不用和应用线程(可能正在对这些对象应用enqueue(),通过显式调用Reference.enqueue())相互影响,garbage collectors会将发现了的Reference对象通过discovered属性链接起来。该discovered属性也用来在pending list中将pending的对象链接起来。
ReferenceHandler是一个高优先级的线程,在Reference类初始化时就启动了,是一个后台常驻的线程。用于不断的从Reference.pending代表的pending list中取出处于Pending状态的节点并判断,其会在一个循环中不断执行方法tryHandlePending(false),该方法代码为:
static boolean tryHandlePending(boolean waitForNotify) {
Reference
其代码逻辑描述如下:
Reference类加载并初始化时,在执行clinit<>()类初始化构造函数中,会得到当前线程最顶层的threadGroup,并通过该threadGroup创建一个ReferenceHandler线程,其名称为"Reference Handler",并将其优先级设置为最高10,作为后台Deamon线程启动。接着通过SharedSecrets机制,将其执行逻辑tryHandlePending(false)开放给其他模块使用(例如nio的Bits类会使用),代码如下:
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
/* If there were a special system-only priority greater than
* MAX_PRIORITY, it would be used here
*/
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
当java VM的GC检测到reference对象出现了合适的可达性变化之后,会与JDK的ReferenceHandler线程协作将其加入到其关联(构建时注册的)的ReferenceQueue中。其属性说明如下:
//两个预定义的空队列,如上所述用于结合其他属性来表示Reference对象的状态
static ReferenceQueue
其定义的方法包括enqueue(Reference r), poll(), 和remove()/remove(timeout),用于入列与出列操作。
其中enqueue(Reference r)用于将Reference对象r入列,该方法只会被Reference类型调用,用于将自己入列(Reference类的enqueue()方法会由java代码调用),gc会直接执行入列逻辑,不需要调用Reference类的对应方法。代码如下:
boolean enqueue(Reference extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// Check that since getting the lock this reference hasn't already been
// enqueued (and even then removed)
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;
}
}
其执行逻辑简单描述如下:
其poll()方法当队列为空时会直接返回null(而非阻塞),否则首先获取同步锁,接着执行reallyPoll()实际执行出列行为,代码如下:
public Reference extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
其remove(timeout)方法,首先获得锁,然后执行reallyPoll()行为实际执行出列得到Reference r,若r不为null则直接返回。否则,首先记下当前时间,并在无限循环中等待lock.wait(timeout),若被唤醒(参见上面enqueue(r)方法),则执行r = reallyPoll(),若不为null则返回,否则,若超时则返回null;若没有超时,则继续循环等待:
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;
}
}
}
}
remove()方法调用了remove(0),意味着永远不会超时,一直等待直到返回对象为止。
私有方法reallyPoll()方法用于实际执行从队列中取出一个对象,若队列为空则直接返回null(该函数必须在一个lock保护的临界区中执行,因为其并非线程安全),代码如下:
private Reference extends T> reallyPoll() { /* Must hold lock */
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;
}
reallyPoll()执行逻辑描述如下:
Cleaner继承了PhantomReference
其类持有类变量static final ReferenceQueue dummyQueue,及类变量static Cleaner first。其中,dummyQueue只是为了与普通Reference表述一致(在jvm gc中表述一致),其实并不实际承担入列行为。实际的Cleaner对象队列由Cleaner.first静态变量作为链表头统一维护。(也就是说,DirectByteBuffer在构建时通过Cleaner.first链表进行维护,并在gc回收时不走常规的入列ReferenceQueue再从ReferenceQueue中取出处理的流程,而是直接从Cleaner.first队列中删除,并执行其cleaner.thunk.run()逻辑)
另持有对象变量Cleaner next, Cleaner pre及Runnable thunk。每个Cleaner对象通过next, pre构成一个双向链表,其表头为Cleaner.first(全局唯一),并关联一个Runnable thunk,在Cleaner节点对象创建时赋予:对于DirectByteBuffer持有的Cleaner而言,为其注册的Runnable为Deallocator或Runnable unmapper(对于使用mmap方式构建的DirectByteBuffer而言)。
对于Cleaner类型,首先其继承了PhantomReference且在初始化时就注册了ReferenceQueue dummyQueue,因此当jvm的gc在发现其不可达后会将其通过disovered属性链入Reference.pending的链表中。而在ReferenceHandler线程执行tryHandlePending()时,若发现该Reference类型为Cleaner,则直接走快速处理逻辑(fast path),直接调用其cleaner.clean()方法,其中会将该cleaner从Cleaner类维护的链表中删除,并执行其cleaner.thunk.run()。而非像普通Reference对象一样,执行r.queue.enqueue(r)入列到自己注册的queue中。
在JDK8以前,在内部Cleaner类型是为DirectByteBuffer专门使用,为了加快其gc时的表现,因此专门定制了其处理逻辑,走快速通道(参见Reference Handler常驻线程的执行逻辑)。另外,通过Cleaner.create(obj, runnable)方法可以在应用中通过显式调用使用该机制。
在Java 9中,终结方法(Finalizer)已经被遗弃了,但它们仍被Java类库使用,相应用来替代终结方法的是清理方法(cleaner)。比起终结方法,清理方法相对安全点,但仍是不可以预知的,运行慢的,而且一般情况下是不必要的。JDK9中有很多原来使用覆盖Object#finalize()方法的清理工作实现都替换为Cleaner实现了,但是仍然不鼓励使用这种方式。
FinalReference继承自Reference,是内置的用来实现java finalization机制的一种引用类型,并不开放给外部应用程序使用。
Finalizer继承自FinalReference,用于实现普通对象由于实现了finalize()方法而需要在gc时对象被回收前执行一些逻辑的功能。Finalizer机制保证对象的finalize()方法一定会被调用(但不保证执行完成)。
其内部持有类变量static ReferenceQueue queue和static Finalizer unfinalized,其中queue是Finalizer对象作为Reference对象,其本身同样遵守jvm在gc时将其放入pending list中,并由Reference Handler线程将其入列的行为(实际的入列,而非Cleaner的快速处理)。那为何还要维护一个unfinalized队列呢?原来所有的Finalizer对象在创建时都会链入该unfinalized链表中,以记录该对象属于无论如何(就算进程要退出)都需要被调用finalize()方法的对象。因此,专门有一个unfinalized链表用于保存这些对象,并在必要时(例如对象尚未被gc回收,但是进程要退出),遍历该链表并逐个调用其finalize()方法。相对的,被gc标记而入列的Finalizer对象会由常驻的Finalizer线程执行其出列及finalize()方法调用。
另持有实例变量next和prev用于将unfinalized链表构建为一个双向链表(此处的双向链表,是为了方便节点随机删除),还持有一个普通对象锁lock(用于保护unfinalized链表的操作)。
一个Finalizer对象已经被执行了析构(finalized)的标志是next==prev==this,在其被从unfinalized队列中删除时就是其被finalized时,此时会将其next与prev都置为this。
其register(Object finalizee)方法会被jvm调用,当对象初始化时,若jvm判断出其需要被finalize(),则会调用该方法将其封装为一个Finalizer对象并添加到Finalizer.unfinalized双向链表中,并为其注册ReferenceQueue queue:
/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
private Finalizer(Object finalizee) {
super(finalizee, queue);
add();
}
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
其runFinalization()方法会被Runtime.runFinalization()调用,用于显式调用finalization的行为。其中会在forkSecondaryFinalizer(runnable)方法中构建一个新线程来跑runnable,其run()行为是从queue中逐个poll()出已入列的Finalizer f,并调用其finalize()方法。其中又借助了看上去高大上的SharedSecrets与JavaLangAccess jla,调用的f.runFinalizer(jla)中最终调用的就是jla.invokeFinalizer(r)(其实际逻辑就是r.finalize()),只是f.runFinalizer(jla)中还包含了一些其他逻辑(后面详述)。一旦发现了队列为空则立刻break退出。代码如下:
/* Called by Runtime.runFinalization() */
static void runFinalization() {
if (!VM.isBooted()) {
return;
}
forkSecondaryFinalizer(new Runnable() {
private volatile boolean running;
public void run() {
// in case of recursive call to run()
if (running)
return;
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
Finalizer f = (Finalizer)queue.poll();
if (f == null) break;
f.runFinalizer(jla);
}
}
});
}
其runAllFinalizers()方法会被Shutdown调用,也就是说进程退出之前会调用该方法。其中会在forkSecondaryFinalizer(runnable)中构建一个新线程执行runnable,其run()行为是遍历unfinalized链表,对其中的每一个Finalizer f,调用其finalize()方法,同样通过f.runFinalizer(jla)->最终调用到f.finalize()。一旦发现unfinalized链表为空之后,立刻break退出:
/* Invoked by java.lang.Shutdown */
static void runAllFinalizers() {
if (!VM.isBooted()) {
return;
}
forkSecondaryFinalizer(new Runnable() {
private volatile boolean running;
public void run() {
// in case of recursive call to run()
if (running)
return;
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
Finalizer f;
synchronized (lock) {
f = unfinalized;
if (f == null) break;
unfinalized = f.next;
}
f.runFinalizer(jla);
}}});
}
其私有的runFinalizer(JavaLangAccess jla)方法供runFinalization()与runAllFinalizers()方法调用(还有Finalizer线程),代码如下:
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}
runFinalizer方法执行逻辑为:
在Finalizer类中声明了一个FinalizerThread,命名为"Finalizer",其run方法逻辑为在无限循环中通过queue.remove()阻塞的获取queue中入列的Finalizer对象f,若得到了则执行其f.runFinalizer(jla)(逻辑如上所述)。该线程捕捉interrupt异常并ignore,因此会永久处于循环状态,也就是说只要java进程未退出,则该线程会一直常驻。
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
该线程优先级被设置为MAX_PRIORITY-2,并在Finalizer类初始化时在clinit<>()方法中启动:
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
由上所述,在java进程中会存在两个常驻线程:
通过jstack也可以确认每一个java进程确实都有Reference Handler和Finalizer这两个常驻线程:
......
"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fabc580b000 nid=0x4503 in Object.wait() [0x0000700008b14000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007b5730518> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000007b5730518> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007fabc600c800 nid=0x3103 in Object.wait() [0x0000700008a11000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007b5740688> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000007b5740688> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
综合上述可得,JDK结合JVM GC对对象的回收行为可以 分为如下五大类:
JVM GC判断所有GC Root不可达的对象,并将其中的注册了ReferenceQueue的Reference类型的对象链入pending list中(上文提到的);将其他的状态直接置为Inactive或直接清理。各引用类型对象的垃圾回收行为描述如下:
普通Object在GC Root不可达之后会被直接执行标记(mark),并在清理阶段清理掉,并不具备Reference引用对象的各种状态
未注册ReferenceQueue的Reference对象,在被gc判断出GC Root不可达之后会被直接设置为Inactive状态
DirectByteBuffer在构建时,jdk内部会为其创建一个Cleaner对象,并注册一个dummyQueue,然后将该cleaner添加到Cleaner.first为head的双向链表中。当gc判断出DirectByteBuffer为GC Root不可达之后会将其对应的Cleaner放入pending list中。而常驻线程Reference Handler在处理Cleaner c时并不会将其入列,而是直接调用c.clean()快速处理方法,其中会从Cleaner.first链中删除该节点,并调用其c.thunk.run()完成clean逻辑
注册了ReferenceQueue的Reference对象,被gc处理后会被放入pending list中(Reference.pending为head),常驻线程Reference Handler在处理Reference r时,会将其入列自己注册的ReferenceQueue q:r.queue.enqueue(r)。此后,对该ReferenceQueue中对象的监控及后续处理就交给应用自行实现(开放给应用程序来定制,例如netty的ObjectCleaner,及guava的FinalizableReference/FinalizableReferenceQueue都是具体的定制案例)
实现了finalize()方法的对象,在对象创建时由jvm自行判断出该对象需要执行finalize逻辑,因此将其封装为一个Finalizer对象并为其注册Finalizer.queue队列,然后将其链入Finalizer.unfinalized链表中。当被gc处理之后,会将该Finalizer对象放入pending list中,并由常驻线程Reference Handler放入Finalizer.queue中(入列)。接着入列的对象会由另外一个常驻线程Finalizer逐个处理,执行其finalize()方法中的逻辑(处理之后会从Finalizer.unfinalized队列中删除)。另外在shutdown时,会从Finalizer.unfinalized链表中逐个取出Finalizer f,并执行其f.finalize()逻辑,以确保每一个实现了finalize()方法的对象的finalize()方法都会被调用一次
如上对于注册了ReferenceQueue的Reference对象在垃圾回收时的行为说明,其对于入列ReferenceQueue之后对相关引用对象的监控及后续处理就开放给应用自行实现,因此可以通过扩展该功能实现自己的逻辑。
ObjectCleaner是netty提供的用于给对象object注册其被回收时需要被执行逻辑,最初netty会在设置FastThreadLocal对象的值时默认将当前线程注册进ObjectCleaner中,其被gc回收时调用的逻辑是remove(threadLocalMap)。现在对这部分逻辑做了修改,InternalThreadLocalMap threadLocalMap会在FastThreadLocalThread线程中自动管理释放,但是若在普通用户线程中使用netty提供的FastThreadLocal时,就需要自己管理FastThreadLocal数据的清理,此时可以通过ObjectCleaner来注册清理逻辑FastThreadLocal.removeAll(),需要特别注意的是在线程池中线程对象并不会被实际回收,此时注册的对象不应该是线程本身,而应该是一个本线程执行完毕后就会被gc收回的对象,否则仍然会导致FastThreadLocal数据无法释放。
netty的ObjectCleaner实现部分借鉴了jdk的Cleaner及Finalizer的思路,通过显示调用ObjectCleaner.register(Object obj, Runnable cleanupTask)来显式管理对象在被回收时应执行的逻辑。目前netty自身并未使用该方案,由应用自行决定使用。
其自身维护了一个ConcurrentSet
public void run() {
boolean interrupted = false;
for (;;) {
// Keep on processing as long as the LIVE_SET is not empty and once it becomes empty
// See if we can let this thread complete.
while (!LIVE_SET.isEmpty()) {
final AutomaticCleanerReference reference;
try {
reference = (AutomaticCleanerReference) REFERENCE_QUEUE.remove(REFERENCE_QUEUE_POLL_TIMEOUT_MS);
} catch (InterruptedException ex) {
// Just consume and move on
interrupted = true;
continue;
}
if (reference != null) {
try {
reference.cleanup();
} catch (Throwable ignored) {
// ignore exceptions, and don't log in case the logger throws an exception, blocks, or has
// other unexpected side effects.
}
LIVE_SET.remove(reference);
}
}
CLEANER_RUNNING.set(false);
// Its important to first access the LIVE_SET and then CLEANER_RUNNING to ensure correct
// behavior in multi-threaded environments.
if (LIVE_SET.isEmpty() || !CLEANER_RUNNING.compareAndSet(false, true)) {
// There was nothing added after we set STARTED to false or some other cleanup Thread
// was started already so its safe to let this Thread complete now.
break;
}
}
if (interrupted) {
// As we caught the InterruptedException above we should mark the Thread as interrupted.
Thread.currentThread().interrupt();
}
}
其执行逻辑为,在无限循环中执行如下:
其对外提供的static void register(Object obj, Runnable cleanupTask)方法代码如下:
public static void register(Object object, Runnable cleanupTask) {
AutomaticCleanerReference reference = new AutomaticCleanerReference(object,
ObjectUtil.checkNotNull(cleanupTask, "cleanupTask"));
// Its important to add the reference to the LIVE_SET before we access CLEANER_RUNNING to ensure correct
// behavior in multi-threaded environments.
LIVE_SET.add(reference);
// Check if there is already a cleaner running.
if (CLEANER_RUNNING.compareAndSet(false, true)) {
final Thread cleanupThread = new FastThreadLocalThread(CLEANER_TASK);
cleanupThread.setPriority(Thread.MIN_PRIORITY);
// Set to null to ensure we not create classloader leaks by holding a strong reference to the inherited
// classloader.
// See:
// - https://github.com/netty/netty/issues/7290
// - https://bugs.openjdk.java.net/browse/JDK-7008595
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Void run() {
cleanupThread.setContextClassLoader(null);
return null;
}
});
cleanupThread.setName(CLEANER_THREAD_NAME);
// Mark this as a daemon thread to ensure that we the JVM can exit if this is the only thread that is
// running.
cleanupThread.setDaemon(true);
cleanupThread.start();
}
}
其执行逻辑为:
ObjectCleaner内部使用的AutomaticCleanerReference继承了WeakReference
AutomaticCleanerReference实例在初始化时,将参数Object obj构建为一个WeakReference并为其注册队列REFERENCE_QUEUE,并将参数cleanupTask赋予this.cleanupTask。其cleanup()方法就是执行cleanupTask.run(),其重写了WeakReference的get()方法,直接返回null(与直接继承PhantomReference没什么区别)。另外,重写了Reference的clear()方法,其行为除了调用super.clear()方法以外,还从LIVE_SET中删除本对象。
对guava FinalizableReference/FinalizableReferenceQueue的使用及源代码分析请参见另一篇文章《使用ReferenceQueue实现对ClassLoader垃圾回收过程的观察、以及由此引发的ClassLoader内存泄露的场景及排查过程》,此处不再赘述。
Reference/ReferenceQueue机制是与JVM GC紧密协作实现的,用于支持不同强度的引用对象在垃圾回收中的不同行为。另外JDK自身基于较弱的引用(Weak,Phantom)不会影响到被引用对象在gc中的生存周期这一特性,扩展并实现了诸如对象析构方法的支持,以及nio DirectByteBuffer被gc回收时自动内存释放等等功能。此外,一些著名的第三方类库也利用这种特性实现了自己定制的对象回收逻辑处理机制。因此,正确的了解这一机制背后的原理有助于帮助我们在特定的场景中更优雅合理的使用各种引用。