Reference类简介

1 状态机
状态转移图

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

Reference类简介
文章图片
状态转移图 说明:
  1. active。此状态需要收集器进行特殊处理。当收集器检测到referent的可达性发生改变,它会将实例的状态改为为pendinginactive,这取决于实例在创建时是否已注册到某个队列。有注册队列情况,实例会添加到pending-Reference列表中,等待入队。新建引用实例是活动状态。
  2. pending。表示pending-Reference列表的元素,等待被Reference-handler处理并入队。未注册引用实例不会有此状态。。
  3. enqueued。表示在注册队列中的元素。当实例从ReferenceQueue中移除时,它将变为inactive。未注册引用实例不会有此状态。尽管Reference提供了入队方法,但是收集器执行的入队操作是直接执行的,而不是调用Reference的入队方法。
  4. inactive。终止态,不会在改变。
【Reference类简介】状态编码
引用的状态是通过不同字段值共同体现的。
状态 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 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 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

    推荐阅读