深入理解Java中的引用(一)——Reference

深入理解Java中的引用(一)——Reference

本系列文章首先会介绍Reference类,为之后介绍的强引用、软引用、弱引用和虚引用打下基础。
最后会介绍虚引用在DirectBuffer回收中的应用。

引用(Reference)

在介绍四种不同类型的引用之前先看一下他们的父类:java.lang.ref.Reference。看以看到Reference 有五个成员变量:referent,queue,pending,next,discovered。下面会一一介绍各个成员变量的作用。

Reference状态

在介绍五个成员变量之前,首页要明确一点,Reference是有状态的,Reference对象的一共有四种状态。如下图所示


image.png

Reference对象所处状态不同,成员变量的值也会变化。

  • Active: Reference对象新建时处于该状态。
  • Pending: 当Reference包装的referent = null的时候,JVM会把Reference设置成pending状态。如果Reference创建时指定了ReferenceQueue,那么会被ReferenceHandler线程处理进入到ReferenceQueue队列中,如果没有就进入Inactive状态。
  • Enqueue: 进入ReferenceQueue中的对象,等待被回收
  • Inactive: Reference对象从ReferenceQueue取出来并被处理掉。处于Inactive的Reference对象状态不能再改变

核心成员变量

1)referent: 表示被包装的对象
下面代码中new Object()就是被包装的对象。

WeakReference<Object> wo = new WeakReference<Object>(new Object());

2) queue: 表示被包装的对象被回收时,需要被通知的队列,该队列在Reference构造函数中指定。当referent被回收的时候,Reference对象就处在了Pending状态,Reference会被放入到该队列中,如果构造函数没有指定队列,那么就进入Inactive状态。

volatile ReferenceQueue<? super T> queue;
......
    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

3)pending: 表示等待被加入到queue的Reference 列表。

    /* List of References waiting to be enqueued.  The collector adds
     * References to this list, while the Reference-handler thread removes
     * them.  This list is protected by the above lock object. The
     * list uses the discovered field to link its elements.
     */
    private static Reference<Object> pending = null;

pending理解链表有点费解,因为代码层面上看这明明就是Reference对象。其实当Reference处在Pending状态时,他的pending字段被赋值成了下一个要处理的对象(即下面讲的discovered),通过discovered可以拿到下一个对象并且赋值给pending,直到最后一个,所以这里就可以把它当成一个链表。而discovered是JVM的垃圾回收器添加进去的,大家可以不用关心底层细节。

4)discovered: 当处于Reference处在pending状态:discovered为pending集合中的下一个元素;其他状态:discovered为null

    /* When active:   next element in a discovered reference list maintained by GC (or this if last)
     *     pending:   next element in the pending list (or null if last)
     *   otherwise:   NULL
     */
    transient private Reference<T> discovered;  /* used by VM */

上述discovered与pending的关系可以用下图表示

image.png

5) next: 当Reference对象在queue中时(即Reference处于Enqueued状态),next描述当前引用节点所存储的下一个即将被处理的节点。

   /* When active:   NULL
     *     pending:   this
     *    Enqueued:   next reference in queue (or this if last)
     *    Inactive:   this
     */
    @SuppressWarnings("rawtypes")
    Reference next;

ReferenceHandler线程会把pending状态的Reference放入ReferenceQueue中,上面说的next,discovered 字段在入队之后也会发生变化,下一小节会介绍。

ReferenceQueue入队过程

上面说到ReferenceHandler线程会把pending状态的Reference对象放入到ReferenceQueue队列中。
查看ReferenceQueue中入队源代码。

    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;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            //设置queue状态
            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;
        }
    }

可以看到入队的Reference节点r进入队列,Reference节点被放在队列头,所以这是一个先进后出队列。 入队的示意图如下:

image.png

ReferenceHandler线程

Reference类中另一个比较重要的成员是ReferenceHandler。ReferenceHandler是一个线程。当JVM加载Reference的时候,就会启动这个线程。用jstack查看该线程栈可以看到。Reference Handler是JVM中的2号线程,并且线性优先级被设置为高优先级

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f7718075800 nid=0x10244 in Object.wait() [0x00007f7708363000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d55b9580> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x00000000d55b9580> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

看源代码他是如何工作的:

    private static class ReferenceHandler extends Thread {

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            for (;;) {
                Reference<Object> r;
                synchronized (lock) {
                    if (pending != null) {
                        r = pending;
                        pending = r.discovered;
                        r.discovered = null;
                    } else {
                        try {
                            try {
                                lock.wait();
                            } catch (OutOfMemoryError x) { }
                        } catch (InterruptedException x) { }
                        continue;
                    }
                }

                // Fast path for cleaners
                if (r instanceof Cleaner) {
                    ((Cleaner)r).clean();
                    continue;
                }
                ReferenceQueue<Object> q = r.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(r);
            }
        }
    }

通过上面代码可以看到ReferenceHandler线程做的是不断的检查pending是否为null, 如果不为null,将pending对象进行入队操作,而pending的赋值由JVM操作。所以ReferenceQueue在这里作为JVM与上层Reference对象管理之间的消息传递方式

总结

  • 介绍了Reference具有的Acitive、Pending、Inactive、Enqueued四种状态,以及他们之间的转化。
  • 分析Reference的各个成员变量的作用。
  • 通过源码解读,描述ReferenceQueue入队过程,ReferenceQueue可以看做是JVM与Reference对象管理之间的桥梁
  • ReferenceHandler作为守护线程将pending状态的Reference节点加入到ReferenceQueue中(如果在Reference构造函数中指定了ReferenceQueue的话)。

下一篇文章会介绍Reference的各个子类:强引用、软引用、弱引用、虚引用。


PS:参考文献
http://www.importnew.com/26250.html
https://zhuanlan.zhihu.com/p/29254258

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

推荐阅读更多精彩内容

  • 引用的分类 Java 1.2以后,除了普通的引用外,Java还定义了软引用、弱引用、虚引用等概念。 强引用:GC ...
    刘惜有阅读 755评论 0 1
  • ReferenceQueue 引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中 ...
    tomas家的小拨浪鼓阅读 35,793评论 10 59
  • 引用类型 JDK1.2之后,Java扩充了引用的概念,将引用分为强引用、软引用、弱引用和虚引用四种。 强引用类似于...
    德彪阅读 4,338评论 0 10
  • 一部适合旅行或者休息的空闲时光阅读的短故事。 等车的间隙,随手准备的短故事,以便打发这样短暂的无聊时光。 听说,最...
    锦肆1994阅读 343评论 0 1
  • 青春里写的都是无奈 那不可预知的未来 吓破了多少人的胆 击碎多少痴情人的梦 我 就要一头钻进你的怀 哪怕身心俱碎 ...
    辟邪仙子阅读 137评论 0 0