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<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());
}