Reference类简介
1 状态机
状态转移图
@startuml
[*] -> active: 新建
active -down-> inactive: 可达性改变且无注册队列
active -right-> pending: 可达性改变且有注册队列
pending -down-> enqueued: 成功加入队列
enqueued -left-> inactive: 从队列移除
inactive -left-> [*]
@enduml
文章图片
状态转移图 说明:
-
active
。此状态需要收集器进行特殊处理。当收集器检测到referent的可达性发生改变,它会将实例的状态改为为pending
或inactive
,这取决于实例在创建时是否已注册到某个队列。有注册队列情况,实例会添加到pending-Reference列表中,等待入队。新建引用实例是活动状态。 -
pending
。表示pending-Reference列表的元素,等待被Reference-handler处理并入队。未注册引用实例不会有此状态。。 -
enqueued
。表示在注册队列中的元素。当实例从ReferenceQueue
中移除时,它将变为inactive
。未注册引用实例不会有此状态。尽管Reference
提供了入队方法,但是收集器执行的入队操作是直接执行的,而不是调用Reference
的入队方法。 -
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
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());
}
问题
参考资料
- Reference
- package-summary.html#reachability
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 为什么你的路演总会超时()
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- thinkphp|thinkphp 3.2 如何调用第三方类库
- 使用composer自动加载类文件
- 一个健康的APP和健全的人格大体类似
- 种树郭橐驼传(文言句式+古今异义+词类活用+通假字)
- 归乡-序章(世界伊始,人类无所依靠,我的故事就从这里开始...)
- jQuery插件
- java简介|Java是什么(Java能用来干什么?)