Reference类简介

1 状态机

状态转移图

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

说明:

  1. active。此状态需要收集器进行特殊处理。当收集器检测到referent的可达性发生改变,它会将实例的状态改为为pending或inactive,这取决于实例在创建时是否已注册到某个队列。有注册队列情况,实例会添加到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对象状态会正常经历finalizable、finalized,进而被回收 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
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容