美文网首页
Java引用类型之:Reference源码解析

Java引用类型之:Reference源码解析

作者: 贪睡的企鹅 | 来源:发表于2019-07-14 15:42 被阅读0次

    1 简介

    Reference是所有引用类型的父类,定义了引用的公共行为和操作,

    2 Reference类结构

    image

    Reference类与垃圾回收是密切配合的,所以该类不能被直接子类化。简单来讲,Reference的继承类都是经过严格设计的,甚至连成员变量的先后顺序都不能改变,所以在代码中直接继承Reference类是没有任何意义的。但是可以继承Reference类的子类。

    例如:Finalizer 继承自 FinalReference,Cleaner 继承自 PhantomReference

    3 核心属性

    3.1 referent引用

    referent对象内部存在引用(referent)用来保存引用对象的地址。

    public abstract class Reference<T>{
    //...省略代码
    /**
     * referent引用
     */
    private T referent;  
    //...省略代码
    }
    
    
    3.2 pending队列

    reference对象内部维护着一个pending单项链表队列,当referenc引用对象被判断可以回收时JVM会将reference对象放入此队列。这里需要注意pending是静态变量,意味着是所有Reference对象共享的。所有referenc对象都会放入同一个队列中。

    public abstract class Reference<T>{
    //...省略代码
        /**
         * pending单项链表队列(就pending表示head指针)
         */
        private static Reference<Object> pending = null;
    
        /**
         * discovered作为pending队列中每一个Reference指向下一个的引用
         */
        transient private Reference<T> discovered;
    }
    
    image
    3.3 referenceQueue队列

    reference对象内部维护着一个单项链表队列,当referenc引用对象被判断可以回收时,reference对象内部ReferenceHandler线程会将 reference对象从pending队列取出放入referenceQueue这个队列中。通过监控这个队列,取出这个reference对象,就可以对reference对象进行一些善后处理。

    实现referenceQueue上是一个单项链表结构,其head指针在ReferenceQueue内部,reference对象内部存在一个属性next将链表节点串联起来

    public abstract class Reference<T>{
    //...省略代码
        /**
         * referenceQueue单项链表队列(head指针在ReferenceQueue内部)
         */
        volatile ReferenceQueue<? super T> queue;
    
        /**
         * next作为queue队列中每一个Reference指向下一个的引用
         */
        @SuppressWarnings("rawtypes")
        Reference next;
    //...省略代码
    }
    
    public class ReferenceQueue<T> {
    
        /** 标识Reference对象状态(从ReferenceQueue移除) **/
        static ReferenceQueue<Object> NULL = new Null<>();
        
        /** 标识Reference对象状态(加入ReferenceQueue队列) **/
        static ReferenceQueue<Object> ENQUEUED = new Null<>();
    
        /**
         * 链表头部引用
         */
        private volatile Reference<? extends T> head = null;
    
        /**
         * 链表节点长度
         */
        private long queueLength = 0;
        //...省略代码
    }    
    
    image

    入队操作

    **
         * 这个方法仅会被Reference中ReferenceHandler线程调用
         * 将Reference加入ReferenceQueue队列中
         */
        boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
            synchronized (lock) {
                /**
                 * 如果Reference对象没有设置queue,或者已经加入ReferenceQueue队列中 (queue == ENQUEUED)
                 * 直接返回false
                 * **/
                ReferenceQueue<?> queue = r.queue;
                if ((queue == NULL) || (queue == ENQUEUED)) {
                    return false;
                }
    
                assert queue == this;
    
                /** 标识Reference对象加入ReferenceQueue队列   **/
                r.queue = ENQUEUED;
                /** 将Reference加入ReferenceQueue队列中 **/
                r.next = (head == null) ? r : head;
                head = r;
                /** 队列数量+1 **/
                queueLength++;
                /** 如果Reference对象为FinalReference 引用数量+1 **/
                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();
            }
        }
    
        @SuppressWarnings("unchecked")
        private Reference<? extends T> reallyPoll() {       /* Must hold lock */
            Reference<? extends T> r = head;
            /** 将Reference从ReferenceQueue队列中取出 **/
            if (r != null) {
                /** 获取ReferenceQueue队列head之后Reference对象 **/
                head = (r.next == r) ?
                    null :
                    r.next;
    
                /** 标识Reference对象从ReferenceQueue队列中被取出  **/
                r.queue = NULL;
                r.next = r;
    
                /** 队列数量+1 **/
                queueLength--;
    
                /** 如果Reference对象为FinalReference 引用数量+1 **/
                if (r instanceof FinalReference) {
                    sun.misc.VM.addFinalRefCount(-1);
                }
                /** 返回 **/
                return r;
            }
            return null;
        }
    

    删除操作

     /**
         * 删除队列元素
         */
        public Reference<? extends T> remove() throws InterruptedException {
            return remove(0);
        }
    
    
        /**
         * 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象或者超时才会返回
         *  timeout时间的单位是毫秒
         */
        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;
                    }
                }
            }
        }
    

    应用场景

    ReferenceQueue一般用来与SoftReference、WeakReference或者PhantomReference配合使用,将需要关注的引用对象注册到引用队列后,便可以通过监控该队列来判断关注的对象是否被回收,从而执行相应的方法。

    1 使用引用队列进行数据监控什么时候回收

    当我们使用有限的堆空间,不断创建大的对象,并将大对象被弱引用唯一指向,由于是弱可达对象在内存不足时gc都会清理,我们通过一个线程不断监听ReferenceQueue中数据,感知哪些对象被回收

    //-verbose:gc -Xms4m -Xmx4m -Xmn2m
    public class ReferenceQueueTest {
    
        private static ReferenceQueue<byte[]> rq = new ReferenceQueue<>();
        private static int _1M = 1024 * 1024;
    
        public static void main(String[] args) {
            Object value = new Object();
            Map<WeakReference<byte[]>, Object> map = new HashMap<>();
            Thread thread = new Thread(ReferenceQueueTest::run);
            thread.setDaemon(true);
            thread.start();
    
            for(int i = 0;i < 100;i++) {
                byte[] bytes = new byte[_1M];
                WeakReference<byte[]> weakReference = new WeakReference<>(bytes, rq);
                map.put(weakReference, value);
                bytes=null;
            }
            System.out.println("map.size->" + map.size());
    
            int aliveNum = 0;
            for (Map.Entry<WeakReference<byte[]>, Object> entry : map.entrySet()){
                if (entry != null){
                    if (entry.getKey().get() != null){
                        aliveNum++;
                    }
                }
            }
            System.out.println("total" + aliveNum);
        }
    
        private static void run() {
            try {
                int n = 0;
                WeakReference k;
                while ((k = (WeakReference) rq.remove()) != null) {
                    System.out.println((++n) + "clear:" + k);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    2 队列监控的反向操作

    反向操作,即意味着一个数据变化了,可以通过Reference对象反向拿到相关的数据,从而进行后续的处理。下面有个小栗子:

    public class ReferenceQueueTest2 {
    
        private static ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
        private static int _1M = 1024 * 1024;
    
        public static void main(String[] args) throws InterruptedException {
            final Map<Object, MyWeakReference> hashMap = new HashMap<>();
            Thread thread = new Thread(() -> {
                try {
                    int n = 0;
                    MyWeakReference k;
                    while(null != (k = (MyWeakReference) referenceQueue.remove())) {
                        System.out.println((++n) + "回收了:" + k);
                        //反向获取,移除对应的entry
                        hashMap.remove(k.key);
                        //额外对key对象作其它处理,比如关闭流,通知操作等
                    }
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
            });
            thread.setDaemon(true);
            thread.start();
    
            for(int i = 0;i < 10000;i++) {
                byte[] bytesKey = new byte[_1M];
                byte[] bytesValue = new byte[_1M];
                hashMap.put(bytesKey, new MyWeakReference(bytesKey, bytesValue, referenceQueue));
            }
        }
    
        static class MyWeakReference extends WeakReference<byte[]> {
            private Object key;
            MyWeakReference(Object key, byte[] referent, ReferenceQueue<? super byte[]> q) {
                super(referent, q);
                this.key = key;
            }
        }
    }
    

    如果没有这个队列,就只能通过不断地轮询reference对象,通过get方法是否返回null( phantomReference对象不能这样做,其get方法始终返回null,因此它只有带queue的构造函数 )来判断对象是否被回收。

    4 reference生命周期

    reference引用对象一共有四种状态,Active(活跃状态)、Pending(半死不活状态)、Enqueued(濒死状态)、Inactive(凉凉状态),

    reference引用对象状态和reference对象状态相互关联,我们可以通过感知reference对象来判断reference指针对象正处于何种状态

    • Active(活跃状态):表示reference引用对象处于活跃正常状态,垃圾回收器会监视这个reference引用对象。当reference引用对象没有任何强引用关联时将会发生改变。

    • Pending(半死不活状态):当reference引用对象没有被任何强引用且其reference对象设置了referenceQueue,JVM将reference对象添加到Pending队列,Pending队列是一个中间队列,用来存放将要放入ReferenceQueue队列的reference对象。

    • Enqueued(濒死状态):每一个Reference对象内部会存在Reference-handler线程,不断轮询判断当前对象是否加入Pending队列,如果已经加入则将Reference对象从Pending队列出队,入队ReferenceQueue队列。并可以针对Reference子类实现做扩展(如Cleaner)

    • Inactive(凉凉状态):JVM会对ReferenceQueue队列中 reference引用对象做清理。此时reference引用对象变为Inactive

    reference引用状态图

    image

    reference对象状态图

    Reference对象生命周期.jpg
    • Active(活跃状态):Reference对象被创建属性next==null

    • Pending(半死不活状态):Reference对象属性next != null,queue != ReferenceQueue.NULL && queue != ReferenceQueue.ENQUEUED,Pending!=null(第一个节点)

    • Enqueued(濒死状态):Reference对象属性next!=null,queue == ReferenceQueue.ENQUEUED

    • Inactive(凉凉状态):Reference对象属性next!=null,queue == ReferenceQueue.NULL;

    5 ReferenceHandler线程

    ReferenceHandler类是Reference类的一个静态内部类,继承自Thread,所以这条线程就叫它ReferenceHandler线程。
    用来不断轮询判断当前对象是否加入Pending队列,如果已经加入则将Reference对象从Pending队列出队,入队ReferenceQueue队列。并可以针对Reference子类实现做扩展

    初始化启动

    Reference内部通过静态代码块初始化并启动ReferenceHandler线程

    static {
            /** 获取当前线程最高线程父类的分组 **/
            ThreadGroup tg = Thread.currentThread().getThreadGroup();
            for (ThreadGroup tgn = tg;
                 tgn != null;
                 tg = tgn, tgn = tg.getParent());
    
            /** 实例ReferenceHandler并启动ReferenceHandler线程 **/
            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结构

    private static class ReferenceHandler extends Thread {
            /** 确保类已经被初始化 **/
            private static void ensureClassInitialized(Class<?> clazz) {
                try {
                    Class.forName(clazz.getName(), true, clazz.getClassLoader());
                } catch (ClassNotFoundException e) {
                    throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
                }
            }
    
            static {
                /** 初始化InterruptedException **/
                ensureClassInitialized(InterruptedException.class);
                /** 初始化Cleaner **/
                ensureClassInitialized(Cleaner.class);
            }
    
            ReferenceHandler(ThreadGroup g, String name) {
                super(g, name);
            }
    
            /** run **/
            public void run() {
                 /**  死循环调用  **/
                while (true) {
                    tryHandlePending(true);
                }
            }
        }
    

    ReferenceHandler执行逻辑

    判断是当前对象是否加入pending,且是队列首节点,如果是加入ReferenceQueue队列,同时如果Reference实现为Cleaner,调用clean方法做清理工作

    static boolean tryHandlePending(boolean waitForNotify) {
                Reference<Object> r;
                Cleaner c;
                try {
                    synchronized (lock) {
                        /** 判断是当前对象是否加入pending,且是队列首节点 **/
                        if (pending != null) {
                            /** 当前对象从pending队列出队 **/
                            r = pending;
                            pending = r.discovered;
                            r.discovered = null;
                            /** 判断当前对象是否是Cleaner  **/
                            c = r instanceof Cleaner ? (Cleaner) r : null;
                        } else {
                            /** 等待 **/
                            if (waitForNotify) {
                                lock.wait();
                            }
                            // retry if waited
                            return waitForNotify;
                        }
                    }
                } catch (OutOfMemoryError x) {
                    Thread.yield();
                    return true;
                } catch (InterruptedException x) {
                    return true;
                }
    
                /** Cleaner清理工作 **/
                if (c != null) {
                    c.clean();
                    return true;
                }
    
                /** 加入ReferenceQueue队列 **/
                ReferenceQueue<? super Object> q = r.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(r);
                return true;
            }
    

    6 其他方法

      /**
         * referent引用
         */
        public T get() {
            return this.referent;
        }
    
        /**
         * 清理referent引用
         */
        public void clear() {
            this.referent = null;
        }
    
    
        /**
         * referent引用对象是否为Enqueued状态,referent对象添加到ReferenceQueue队列
         */
        public boolean isEnqueued() {
            return (this.queue == ReferenceQueue.ENQUEUED);
        }
    
        /**
         * 将referent对象添加到ReferenceQueue队列
         */
        public boolean enqueue() {
            return this.queue.enqueue(this);
        }
    

    相关文章

      网友评论

          本文标题:Java引用类型之:Reference源码解析

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