【Java】 DirectByteBuffer堆外内存回收( 三 )

ReferenceHandlerReference的内部类 , 继承了Thread , 在run方法中开启了一个循环,不断的执行tryHandlePending方法,处理Reference中pending列表:
public abstract class Reference<T> {private static class ReferenceHandler extends Thread {// ...ReferenceHandler(ThreadGroup g, String name) {super(g, name);}public void run() {while (true) {// 处理pending列表tryHandlePending(true);}}} }Cleaner会启动一个优先级最高的守护线程,不断调用tryHandlePending来检测是否有需要回收的引用对象(还未进行真正的回收),然后进行处理 。
处理pending列表垃圾回收器会将要回收的引用对象放在Referencepending变量中,从数据类型上可以看出pending只是一个Reference类型的对象,并不是一个list , 如果有多个需要回收的对象,如何将它们全部放入pending对象中?
可以把pengding看做是一个链表的头结点,假如有引用对象被判定需要回收,如果pengding为空直接放入即可,如果不为空,将使用头插法将新的对象加入到链表中,也就是将新对象的discovered指向pending对象 , 然后将pending指向当前要回收的这个对象 , 这样就形成了一个链表,pending指向链表的头结点 。

【Java】 DirectByteBuffer堆外内存回收

文章插图
在pending链表中的引用对象处于pending状态 。
接下来看tryHandlePending方法的处理逻辑:
  1. 如果pending不为空,表示有需要回收的对象,此时将pengding指向的对象放在临时变量r中,并判断是否是Cleaner类型 , 如果是将其强制转为Cleaner,记录在临时变量c中,接着更新pending的值为r的discovered,因为discovered中记录了下一个需要被回收的对象,pengding需要指向下一个需要被回收的对象;
    pending如果为NULL,会进入到else的处理逻辑,返回值为参数传入的waitForNotify的值 。
  2. 判断Cleaner对象是否为空 , 如果不为空,调用Cleaner的clean方法进行清理;
  3. 获取引用对象关联的引用队列 , 然后调用enqueue方法将引用对象加入到引用队列中;
  4. 返回true;
public abstract class Reference<T> {// 指向pending列表中的下一个节点transient private Reference<T> discovered;// 静态变量pending列表 , 可以看做是一个链表,pending指向链表的头结点private static Reference<Object> pending = null;static boolean tryHandlePending(boolean waitForNotify) {Reference<Object> r;Cleaner c;try {synchronized (lock) {// 如果pending不为空if (pending != null) {// 获取pending执行的对象r = pending;// 如果是Cleaner类型c = r instanceof Cleaner ? (Cleaner) r : null;// 将pending指向下一个节点pending = r.discovered;// 将discovered置为空r.discovered = null;} else {// 等待if (waitForNotify) {lock.wait();}return waitForNotify;}}} catch (OutOfMemoryError x) {Thread.yield();// retryreturn true;} catch (InterruptedException x) {// retryreturn true;}if (c != null) {// 调用clean方法进行清理c.clean();return true;}// 获取引用队列ReferenceQueue<? super Object> q = r.queue;// 如果队列不为空 , 将对象加入到引用队列中if (q != ReferenceQueue.NULL) q.enqueue(r);// 返回truereturn true;}}释放内存在Cleaner的clean方法中,可以看到,调用了thunk的run方法,前面内容可知 , thunk指向的是Deallocator对象,所以会执行Deallocator的run方法,Deallocator的run方法前面也已经看过,里面会对DirectByteBuffer的堆外内存进行释放:
public class Cleaner extends PhantomReference<Object> {public void clean() {if (!remove(this))return;try {// 调用run方法thunk.run();} catch (final Throwable x) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {if (System.err != null)new Error("Cleaner terminated abnormally", x).printStackTrace();System.exit(1);return null;}});}}}总结
Cleaner是一个虚引用,它实际引用的对象DirectByteBuffer如果被GC判定为需要回收,会将引用该对象的Cleaner加入到pending列表,ReferenceHandler线程会不断检测pending是否为空 , 如果不为空,就对其进行处理:
  1. 如果对象类型为Cleaner,就调用Cleaner的clean方法进行清理,Cleaner的clean方法又会调用Deallocator的run方法,里面调用了freeMemory方法对DirectByteBuffer分配的堆外内存进行释放;
  2. 将Cleaner对象加入到与其关联的引用队列中;

    推荐阅读