美文网首页
reference类源码以及几种引用的原理分析

reference类源码以及几种引用的原理分析

作者: 无聊之园 | 来源:发表于2019-05-12 22:08 被阅读0次

    reference类的有几个很重要的类:FinalReference, SoftReference,WeakReference、PhantomReference.

    FinalReference:这个有且仅有一个子类,Finalizer,这个类是和Object的finalize方法(垃圾回收器回收对象前调用的方法)有关。

    SoftReference: 软引用,当软引用包装的对象只剩下软引用一个引用的时候,当内存充足的时候gc不会对其进行回收。

    WeakReference:弱引用,当软引用包装的对象只剩下软引用一个引用的时候,,只要gc发现了这个引用,就会进行回收。

    PhantomReference:幽灵引用,或者说虚引用。这个引用对其包装的对象没有任何印象,get方法也是直接返回null,好像和包装的对象没有任何关联一样。这个引用的用法也和其他的引用不同。这个引用主要是在gc回收对象的时候,给用户一个通知。

    这几个子类都和Reference父类有着密切的关系。

    Reference的部分源代码

    成员变量

    public abstract class Reference<T> {
          //引用包装的对象,用户传入的
         private T referent;         /* Treated specially by GC */
         // 引用队列,用于记录引用状态,以及对引用链表队列处理
        volatile ReferenceQueue<? super T> queue;
          // reference链表队列的下一个引用
          Reference next;
         // gc操作的变量
        transient private Reference<T> discovered;  /* used by VM */
        // 锁
        static private class Lock { }
        private static Lock lock = new Lock();
        // gc操作的变量,如果包装的对象要进行gc回收,则会将这个引用的pendig对象赋值
        private static Reference<Object> pending = null;
    
    }
    

    ReferenceQueue:Reference对象维护了一个ReferenceQueue队列,这个队列的入队操作的目的是记录一个垃圾回收Reference包装的对象的通知,比如PhantomReference幽灵引用,则需要用户传入自己的ReferenceQueue,然后从ReferenceQueue中获取垃圾回收的标识

    ReferenceQueue还逻辑上分几种,用来标识Reference的状态:
    ENQUEUED:已入队
    NULL: 未入队
    等。。。

    ReferenceQueue的部分源代码

    public class ReferenceQueue<T> {
        private static class Null<S> extends ReferenceQueue<S> {
            boolean enqueue(Reference<? extends S> r) {
                return false;
            }
        }
        // 逻辑意义为null的引用队列,Reference对象的quene对象为这个Null的时候,不进行入队操作
        static ReferenceQueue<Object> NULL = new Null<>();
         // 逻辑意义为已经入队的标识符
        static ReferenceQueue<Object> ENQUEUED = new Null<>();
    
    // 入队操作,真正的队列是Reference的next引用所维护的
    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;
                // reference对象的queue对象为逻辑null则不进行入队处理
                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;
            }
        }
    }
    // 出队操作
     public Reference<? extends T> poll() {
            if (head == null)
                return null;
            synchronized (lock) {
                return reallyPoll();
            }
        }
    

    Reference对象有一个静态代码块:会启动一个守护线程,来处理引用的入队等操作。

    画外音:守护线程和用户线程的区别是,有用户线程存在的时候,jvm不会退出,会继续运行用户线程,即使启动它的main主线程运行完毕了。守护线程则不会影响jvm的退出。

    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);
                }
            });
        }
    
    private static class ReferenceHandler extends Thread {
        public void run() {
                while (true) {
                    tryHandlePending(true);
                }
            }
    }
    // 守护线程会循环调用这个方法
    static boolean tryHandlePending(boolean waitForNotify) {
            Reference<Object> r;
            Cleaner c;
            try {
                synchronized (lock) {
                  // gc回收引用包装的对象的时候,会给引用的pending变量设置,所以pending不会null,则说明gc回收了引用包装的对象
                    if (pending != null) {
                        r = pending;
                        // 'instanceof' might throw OutOfMemoryError sometimes
                        // Cleanner是PhantomReference的子类,用于回收堆外内存的
                        // 堆外内存所对应的java堆对象被gc回收了后,cleanner受到通知,清除堆外内存
                        // so do this before un-linking 'r' from the 'pending' chain...
                        c = r instanceof Cleaner ? (Cleaner) r : null;
                        // unlink 'r' from 'pending' chain
                        pending = r.discovered;
                        r.discovered = null;
                    } else {
                        // The waiting on the lock may cause an OutOfMemoryError
                        // because it may try to allocate exception objects.
                        if (waitForNotify) {
                            lock.wait();
                        }
                        // retry if waited
                        return waitForNotify;
                    }
                }
            } catch (OutOfMemoryError x) {
                // Give other threads CPU time so they hopefully drop some live references
                // and GC reclaims some space.
                // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
                // persistently throws OOME for some time...
                Thread.yield();
                // retry
                return true;
            } catch (InterruptedException x) {
                // retry
                return true;
            }
    
            // Fast path for cleaners
            if (c != null) {
                // 回收堆外内存,调用的是unsafe.freeMemory方法
                c.clean();
                return true;
            }
    
            ReferenceQueue<? super Object> q = r.queue;
            // 如果Reference的队列对象为逻辑null,则说明不需要入队操作,不需要发送给用户的通知,直接不处理
             if (q != ReferenceQueue.NULL) q.enqueue(r);
            return true;
        }
    

    看唯一不同的幽灵引用的使用:自定义队列,然后gc回收的时候,referenceHandler守护线程会发现,引用的pending被gc设置值了,不为null,则进行处理,又发现引用的队列对象不为逻辑null,则将引用加入队列,所以用户可以从队列中获取到值。

    public class TestPhantomReference {
    
            private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
            public static void main(String[] args){
    
                Object obj = new Object();
                PhantomReference<Object> pr = new PhantomReference<Object>(obj, rq);
                 // 输出null
                System.out.println(pr.get());
                obj = null;
                System.gc();
                // 输出null
                System.out.println(pr.get());
                Reference<Object> r = (Reference<Object>)rq.poll();
                // r不为空 
               if(r!=null){
           
                    System.out.println("回收");
                }
            }
        }
    
    

    相关文章

      网友评论

          本文标题:reference类源码以及几种引用的原理分析

          本文链接:https://www.haomeiwen.com/subject/iuyhaqtx.html