前言
WeakHashMap 来实现了一个 LRU 缓存,这是一个比较顶层的具体使用,那么看下底层是如何实现的。
WeakHashMap 中有一个成员变量比较关键:
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
对于 ReferenceQueue,里面有 Reference 类型的成员变量,这也是比较关键的一个类。这篇文章对这两个类做一个总结。
Reference
Reference 4个状态
Reference 即引用,平常所知道的强引用,软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)的父类就是 Reference。
那么,对于这些不同的引用,会有不同的作用,例如:
强引用:即使发生内存溢出也不会释放。
软引用:内存不够了,那么才释放,如果内存够,即使没人用也不会释放。
弱引用:不管内存够不够,只要是没用,发现了那么就回收了。
虚引用:和没有任何引用一样,在任何时候都可能被垃圾回收器回收。主要用来跟踪对象被垃圾回收器回收的活动。
对于 Reference,它的注释上写明了一个 Reference 实例会有以下四种状态之一:
Active:可以理解成可到达状态。新创建的对象都是这个状态。
Pending:如果一个引用实例在创建的时候注册到了某一个引用队列中,那么引用实例所引用的对象的可达性改变的时候(也就是程序不再引用了,被 GC 回收了),那么,这个引用实例原来的状态是 Active,现在就会改成 Pending,然后等待被 Reference-handler 线程处理(也就是等待被改成 Enqueued 状态)。Ps:如果一个引用实例在创建的时候 没有 注册到某一个引用队列中,那么这个引用就不会有这个状态。
Enqueued:引用实例在 ReferenceQueue 中的状态。当一个引用实例被移除出引用队列的时候,那么就会变成 Inactive。
Inactive:引用实例的最终状态,如果一个引用实例到了这个状态,那么状态不会再被改变了。
总的来说,如果一个引用实例创建的时候没有注册到引用队列,那么就不会有 Pending 和 Enqueued 状态。
这里要区分一点,引用实例 和 引用实例所指向的对象。例如:
public class Test {
public static void main(String args[]){
Object obj = new Object();
SoftReference<Object> softReference = new SoftReference<>(obj);
}
}
softReference 对象就是引用实例,obj 对象就是引用实例所指向的对象。再例如:
public class Test {
public static void main(String args[]){
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
Object obj = new Object();
SoftReference<Object> softReference = new SoftReference<>(obj,referenceQueue);
}
}
referenceQueue 就是上文所说的一个引用实例在创建的时候注册到了某一个引用队列中。
那么以上的状态变化,就是指,比如说 obj 对象没有人使用了,如果要被 GC 回收了(还没真正GC),那么 softReference 对象就会被从 Active 状态改为 Pending,然后由专门的线程使其入队列中,变成 Enqueued 状态。最后如果出了队列,那么就会变成 Inactive 状态。
那么,为什么要这样设计,源码中有这样一段注释:
* The state is encoded in the queue and next fields as follows:
*
* Active: queue = ReferenceQueue with which instance is registered, or
* ReferenceQueue.NULL if it was not registered with a queue; next =
* null.
*
* Pending: queue = ReferenceQueue with which instance is registered;
* next = this
*
* Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance
* in queue, or this if at end of list.
*
* Inactive: queue = ReferenceQueue.NULL; next = this.
*
* With this scheme the collector need only examine the next field in order
* to determine whether a Reference instance requires special treatment: If
* the next field is null then the instance is active; if it is non-null,
* then the collector should treat the instance normally.
*
* To ensure that a concurrent collector can discover active Reference
* objects without interfering with application threads that may apply
* the enqueue() method to those objects, collectors should link
* discovered objects through the discovered field. The discovered
* field is also used for linking Reference objects in the pending list.
但是,什么是正常对待,什么是特殊对待,这个作者之前也没搞清楚,有知道的同学希望可以在这里告知一下:what does the “next” field do in java.lang.ref.Reference,现在作者的理解是:特殊对待指的是看需不需要把引用的状态由Active 转化为Pending,正常对待就是直接把 Active 状态改为 Inactive。
另外还要注意一点:上面说的 Pending 的状态,当对象的可达性改变的时候,也就是程序不再引用了,那么就要被改成 Pending 的状态,那么此时是已经回收了,还是打算准备要回收,有回收的资格呢?
根据这篇文章:Java Reference(写得非常好,很清晰)说的:
如果是 SoftReference 和 WeakReference,那么就是还没有真正的被 GC,对象会先进入队列,此时我们可以通过 finalize() 方法重新获得对象。
如果是虚引用 PhantomReference,那么就是已经真正GC 回收了,然后才会进入到队列中,根本无法重新获得目标对象
总结:关于这些状态的变化,盗用 Java WeakHashMap 源码解析 (这篇文章也写得很好,博主好厉害)上的一个图:
ReferenceQueue
根据 Java对象的强、软、弱和虚引用原理+结合ReferenceQueue对象构造Java对象的高速缓存器 里面所说的:
作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。
这个时候 ReferenceQueue 就发挥作用了。
当一个实例对象被 GC 的时候,那么 ReferenceQueue 就加入了指向该被GC的实例对象的软引用对象,那么我们就可以通过:
SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
// 清除ref
}
这样清除掉。
Reference 的应用
以上了解了 Reference 和 ReferenceQueue 的一些知识,现在看看他们的具体应用。首先第一个最主要的应用自然是 WeakHashMap 了。
这篇文章介绍得蛮清除的,这里就不 copy 了:Java WeakHashMap 源码解析,画个图总结一下就是这样子的:
对于 WeakHashMap,也是同样的道理:
当 key 不用的时候,key 就会自动清除,在 WeakHashMap 内部,这个 Entry 就相当于上文所说的 引用(不是引用所指向的实例,Entry 的 key 才是引用所指向的实例),WeakHashMap 的工作实际上就是上文所说的
WeakReference Object 会从 Active 状态变为 Pending 状态,再变为 Enqueued 状态 ,但是不会自动被GC 回收,需要程序员手动 poll(),然后清除掉。
至于具体的 WeakHashMap 的 键值对的清除的实现,就是由 Reference 来进行实现。