Reference类简介

1 状态机

状态转移图

@startuml
[*] -> active: 新建
active -down-> inactive: 可达性改变且无注册队列
active -right-> pending: 可达性改变且有注册队列
pending -down-> enqueued: 成功加入队列
enqueued -left-> inactive: 从队列移除
inactive -left-> [*]
@enduml
状态转移图

说明:

  1. active。此状态需要收集器进行特殊处理。当收集器检测到referent的可达性发生改变,它会将实例的状态改为为pendinginactive,这取决于实例在创建时是否已注册到某个队列。有注册队列情况,实例会添加到pending-Reference列表中,等待入队。新建引用实例是活动状态。
  2. pending。表示pending-Reference列表的元素,等待被Reference-handler处理并入队。未注册引用实例不会有此状态。。
  3. enqueued。表示在注册队列中的元素。当实例从ReferenceQueue中移除时,它将变为inactive。未注册引用实例不会有此状态。尽管Reference提供了入队方法,但是收集器执行的入队操作是直接执行的,而不是调用Reference的入队方法
  4. inactive。终止态,不会在改变。

状态编码
引用的状态是通过不同字段值共同体现的。

状态 queue next discovered
active 有注册队列时ReferenceQueue实例
或者 未注册队列时ReferenceQueue.NULL
NULL 下一个discovered列表元素
或者如果是队尾元素则是this
pending 引用所注册的ReferenceQueue实例 this 下一个pending列表元素
或者如果是队尾元素则是NULL
enqueued ReferenceQueue.ENQUEUED 下一个入队的实例
或者如果是队尾元素则是this
NULL
inactive ReferenceQueue.NULL this NULL

如何判断引用状态是否是active?
基于以上编码关系,垃圾收集器只需要对next字段进行判断,决定是不是需要进行特殊处理。规则如下:如果next字段为NULL,那么实例是active的;否则,收集器将会按照普通情况处理。

discovered字段的用途?
当引用状态为active时,为了保证并发收集器能够发现下一个active引用,同时不影响应用线程对这些active引用执行enqueue操作,收集器使用了discovered字段记录了下一个active引用。
当引用状态为pending时,discovered字段还记录了pending列表中的下一个引用。

引用队列的用途?
如果程序需要感知对象可达性的变化时,那么要在创建引用对象时传入注册队列。当垃圾收集器发现referent可达性发生变化时,会将referent的引用加入到注册队列中。此时,引用处于enqueued状态。程序可以通过阻塞轮询的方式,从队列中移除引用。[2]

已注册引用和引用注册队列的关系是单向的。也就是说,引用注册队列不会跟踪已注册引用的状态。如果已注册引用的状态变为不可达,那么它永远不会进入引用注册队列。所以,程序需要保证referent对象的可达性。

如果保证referent的可达性呢?
一种方式,使用单独的线程轮询,并从队列中删除引用对象,然后对其进行处理。
另一种方式,在执行操作引用时进行检查(lazy)。
例如,使用弱引用实现弱键的哈希表,可以在每次访问时轮询其引用队列。这就是WeakHashMap类的工作原理。由于ReferenceQueue.poll方法只检查内部数据结构,因此,将为哈希表访问方法增加很少的开销。

引用是何时入队的?
收集器在将软引用和弱引用加入注册队列(如果有的话)之前,自动清除软引用和弱引用。因此,软引用和弱引用不需要注册到队列中才能发挥作用,而虚引用则需要注册队列。虚引用对象会保持可达,除非虚引用被清除或者虚引用对象本身不可达。

public static void main(String[] args) {
    ReferenceQueue referenceQueue = new ReferenceQueue();
    PhantomReference phantomReference = // 如果不声明本地变量,在gc后将会入队
            new PhantomReference<>(new Object(), referenceQueue);
    System.gc();
    System.out.println("Object in queue: " + referenceQueue.poll());
}

2 tryHandlePending

处理pending列表中的引用对象。
参考如下代码,可见处理过程中,体现了这样一点。discovered字段记录这pending列表中的下一个元素。

Reference<Object> r;
Cleaner c;
synchronized (lock) {
    if (pending != null) {
        r = pending;
        // 'instanceof' might throw OutOfMemoryError sometimes
        // 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;
    }
}
// Cleaner继承了PhantomReference,用于一些清理工作
if (c != null) {
    c.clean();
    return true;
}

ReferenceQueue<? super Object> q = r.queue;
// 有注册队列,未入队
if (q != ReferenceQueue.NULL) q.enqueue(r);

3 引用类型

类型 强引用 SoftReference WeakReference PhantomReference
定义 如果线程在不遍历任何引用对象的情况下访问某个对象,则该对象是强可达的。
新建的对象是线程强可达的。
如果对象不是强可达的,但可以通过遍历软引用来访问,则该对象是软可达的。 如果对象不是强、软可达的,但可以通过遍历弱引用访问,那么它就是弱可达的。
当对弱可达对象的弱引用被清除时,就要对该对象执行finalization过程。
如果一个对象不是强、软、弱可达的,那么它就是幻象可达的,对象已经被终止(finalized),但是幻象引用指向了它。
用途 普通引用 内存敏感的缓存 map 回收预清理(代替finalization机制)
垃圾回收 不可达时 收集器根据内存情况进行回收,保证在OOM之前回收 referent对象状态会正常经历finalizablefinalized,进而被回收 PhantomReference在收集器确定其referent不需要回收(maybe otherwise be reclaimed)时入队
强度 依次减弱
是否注册队列 - 否(一般) 否(一般)

当对象不属于上述四种可达方式时,成为不可达对象。不可达对象,需要进行回收。

软引用代码示例:

public static void main(String[] args) {
    Reference reference = new SoftReference(new Object());
    System.gc(); // 输出结果非null
    System.out.println("Object is: " + reference.get());
}

弱引用代码示例:

public static void main(String[] args) {
    Reference reference = new WeakReference(new Object());
    System.gc(); // 注释掉时,输出结果非null
    System.out.println("Object is: " + reference.get());
}

幻象引用代码示例:

public static void main(String[] args) {
    Reference reference = new PhantomReference(new Object(), null);
    System.gc(); // 无论是否注释掉,结果始终为null。因为重写了get方法
    System.out.println("Object is: " + reference.get());
}

问题

参考资料

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

推荐阅读更多精彩内容