|  
 别睡!打起精神来学习! 
 📖什么是ReferenceReference本身是一个抽象类,它的实现类有SoftReference,WeakReference,PhantomReference,FinalReference。GC回收器会与该类中的变量做直接交互。 当垃圾收集器检测到referent对象可达性为不可达时,Reference的实例状态将从Active变更为Pending,当实例被ReferenceHandler放入ReferenceQueue中时,它的状态从Pending转换为Enqueued,当ReferenceQueue消费完成后,则变换为最终状态Inactive,此时实例会被释放掉。 Reference用于管理对象自身的四种状态: Active 新创建的Reference的实例状态Pending 实例即将被ReferenceHandler线程加入ReferenceQueue队列的状态Enqueued 实例在ReferenceQueue队列中,处于消费中或等待中的状态Inactive 最终状态
 ??垃圾回收器🚩可达性分析算法JVM判断一个对象是否存活时,默认使用了可达性分析算法。通过GC Root对象向下搜索,搜索到的对象都是可达对象,而剩余对象全都是不可达对象。如下图,红色为不可达对象,蓝色为可达对象。 
 我们可以看到只有在GC Root这条链上并被这条引用链引用的对象才可以被认定为可达对象。 GC Root对象有哪些呢? 本地方法栈JNI引用的对象,也就是Native引用的对象虚拟机栈中引用的对象,也就是栈帧中的本地变量(说白了就是执行方法中new的对象)方法区里常量引用的对象方法区中静态属性引用的对象
 🌲Reference源码解读首先来看一下Reference的核心成员变量 private T referent;
volatile ReferenceQueue<? super T> queue;
volatile Reference next;
transient private Reference<T> discovered;
private static Reference<Object> pending = null;
private static Lock lock = new Lock();
 referent 表示该对象引用的对象实例,在创建引用时就需要指定该实例queue 对象触发回收时需要通知的队列,当触发回收时,会将整个referent实例放入队列中,消费者可以指定队列进行消费next 指向引用队列中的下一个Reference实例discovered 维护引用列表中的下一个元素,当为最后一个元素时,该值为nullpending 等待通知的引用链表,垃圾回收器会将即将回收的对象实例加入到链表中lock 锁对象,用于垃圾收集器的同步操作,防止GC和tryHandlePending同时操作pending
 注意Refrence的静态代码块 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);
        }
    });
}
 可以看到此处主要做的动作就是遍历主线程组中所有线程,并为每个线程创建ReferenceHandler线程对象并设为了守护线程,我们看看它的run方法 public void run() {
    while (true) {
        tryHandlePending(true);
    }
}
 此处循环调用了tryHandlePending方法,其核心逻辑是扫描pending链表是否有数据,如果有,则取出链表头并放入通知队列。 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;
}
 我们对以上整段流程做个总结,有专门的守护线程扫描是否有新的被回收的Reference对象被放入队列了,如果有则将其放入队列中,等待订阅者消费,流程如下图。 
 🌲ReferenceQueue队列源码解读Reference通知队列用的是ReferenceQueue,并不是像LinkedBlockingQueue这样的队列,不用LinkedBlockingQueue的原因在于ReferenceQueue的应用场景仅作为回收后通知,并不存在特别高的并发,因此ReferenceQueue只使用了一把锁。其次ReferenceQueue基于Reference对象实现,天然支持next引用,不像LinkedBlockingQueue实现逻辑那么复杂,还需要维护Node对象。 接下来我们从ReferenceQueue的核心成员变量看起 private Lock lock = new Lock();
private volatile Reference<? extends T> head = null;
private long queueLength = 0;
 lock 全局锁对象head 当前链表的头对象queueLength 当前队列的长度
 我们看一下放入队列的方法enqueue,该方法本质上是将Reference自身放入到自己成员变量的queue队列中 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;
    }
}
 remove方法,提供了等待超时时长
 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;
            }
        }
    }
}
 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;
}
 ??推荐阅读通过本章我们了解了Reference的原理和ReferenceQueue的实现原理,以及为什么要使用ReferenceQueue作为队列。如果你还不知道Reference如何使用可阅读这篇文章:JAVA中的引用类型,强引用软引用弱引用虚拟引用 |