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

引用队列ReferenceQueue名字听起来是一个队列,实际使用了一个链表,使用头插法将加入的节点串起来,ReferenceQueue中的head变量指向链表的头节点,每个节点是一个Reference类型的对象:
public class ReferenceQueue<T> {// head为链表头节点private volatile Reference<? extends T> head = null;}Reference中除了discovered变量之外,还有一个next变量,discovered指向的是处于pending状态时pending列表中的下一个元素,next变量指向的是处于Enqueued状态时,引用队列中的下一个元素:
public abstract class Reference<T> {/* When active:处于active状态时为NULL*pending:this*Enqueued:Enqueued状态时,指向引用队列中的下一个元素*Inactive:this*/@SuppressWarnings("rawtypes")Reference next;/* When active:active状态时 , 指向GC维护的一个discovered链表中的下一个元素*pending:pending状态时,指向pending列表中的下一个元素*otherwise:其他情况为NULL*/transient private Reference<T> discovered;/* used by VM */}

【Java】 DirectByteBuffer堆外内存回收

文章插图
enqueue入队进入引用队列中的引用对象处于enqueue状态 。
enqueue的处理逻辑如下:
  1. 判断要加入的对象关联的引用队列,对队列进行判断 , 如果队列为空或者队列等于ReferenceQueue中的空队列ENQUEUED,表示该对象之前已经加入过队列,不能重复操作,返回false,如果未加入过继续下一步;
  2. 将对象所关联的引用队列置为ENQUEUED,它是一个空队列,表示节点已经加入到队列中;
  3. 判断头节点是否为空,如果为空 , 表示链表还没有节点,将当前对象的next指向自己,如果头结点不为空,将当前对象的next指向头结点,然后更新头结点的值为当前对象(头插法插入链表);
  4. 增加队列的长度 , 也就是链表的长度;
public class ReferenceQueue<T> {// 空队列static ReferenceQueue<Object> ENQUEUED = new Null<>();// 入队,将节点加入引用队列,队列实际上是一个链表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;// 如果头结点为空将下一个节点置为自己,否则将next置为链表的头结点,可以看出同样使用的是头插法将节点插入链表r.next = (head == null) ? r : head;// 更新头结点为当前节点head = r;// 增加长度queueLength++;if (r instanceof FinalReference) {sun.misc.VM.addFinalRefCount(1);}lock.notifyAll();return true;}}}poll出队在调用poll方法从引用队列中获取一个元素并出队的时候,首先对head头结点进行判空,如果为空表示引用队列中没有数据,返回NULL,否则调用reallyPoll从引用队列中获取元素 。
出队的处理逻辑如下:
  1. 获取链表中的第一个节点也就是头结点,如果不为空进行下一步;
  2. 如果头节点的下一个节点是自己,表示链表只有一个节点,头结点出队之后链表为空,所以将头结点的值更新为NULL;
    如果头节点的下一个节点不是自己 , 表示链表中还有其他节点,更新head头节点的值为下一个节点,也就是next指向的对象;
  3. 将需要出队的节点的引用队列置为NULL,next节点置为自己 , 表示节点已从队列中删除;
  4. 引用队列的长度减一;
  5. 返回要出队的节点;
从出队的逻辑中可以看出,引用队列中的对象是后进先出的,poll出队之后的引用对象处于Inactive状态 , 表示可以被GC回收掉 。
public class ReferenceQueue<T> {/*** 从引用队列中获取一个节点,进行出队操作*/public Reference<? extends T> poll() {// 如果头结点为空 , 表示没有数据if (head == null)return null;synchronized (lock) {return reallyPoll();}}@SuppressWarnings("unchecked")private Reference<? extends T> reallyPoll() {、/* Must hold lock */// 获取头结点Reference<? extends T> r = head;if (r != null) {// 如果头结点的下一个节点是自己,表示链表只有一个节点,head置为null,否则head值为r的下一个节点,也就是next指向的对象head = (r.next == r) ?null :r.next;// 将引用队列置为NULLr.queue = NULL;// 下一个节点置为自己r.next = r;// 长度减一queueLength--;if (r instanceof FinalReference) {sun.misc.VM.addFinalRefCount(-1);}// 返回链表中的第一个节点return r;}return null;}}

推荐阅读