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

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);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容