java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se))的总称。
这篇文章主要为大家详细解析了 Java Reference 源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
Reference 对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互。即可以使用 Reference 对象来引用其它对象,但是最后还是会被垃圾收集器回收。程序有时候也需要在对象回收后被通知,以告知对象的可达性发生变更。
Java 提供了四种不同类型的引用,引用级别从高到低分别为 FinalReference,SoftReference,WeakReference,PhantomReference。其中 FinalReference 不对外提供使用。每种类型对应着不同级别的可达性。
简介
强引用 FinalReference
强引用指的是,程序中有直接可达的引用,而不需要通过任何引用对象,如 Object obj = new Object(); 中,obj 为强引用。
软引用 SoftReference
软引用,非强引用,但是可以通过软引用对象来访问。软引用的对象,只有在内存不足的时候(抛出 OOM 异常前),垃圾收集器会决定回收该软引用所指向的对象。软引用通常用于实现内存敏感的缓存。
SoftReference
弱引用 WeakReference
弱引用,非强引用和软引用,但是可以通过弱引用对象来访问。弱引用的对象,不管内存是否足够,只要被垃圾收集器发现,该引用的对象就会被回收。实际的应用见 WeakHashMap 等。
WeakReference
虚引用 PhantomReference
虚引用,该引用必须和引用队列 (ReferenceQueue) 一起使用,一般用于实现追踪垃圾收集器的回收动作,比如在对象被回收的时候,会调用该对象的 finalize 方法,在使用虚引用可以实现该动作,也更加安全。
- Object obj = new Object();
- ReferenceQueue < Object > refQueue = new ReferenceQueue < >();
- PhantomReference < Object > phantom = new PhantomReference < Object > (obj, refQueue);
- ReferenceQueue
该队列作为引用中的一员,可以和上述三种引用类型组合使用,该队列的作用是:创建 Reference 时,将 Queue 注册到 Reference 中,当该 Reference 所引用的对象被垃圾收集器回收时,会将该 Reference 放到该队列中,相当于一种通知机制。
示例 Demo1:
- ReferenceQueue queue = new ReferenceQueue();
- WeakReference reference = new WeakReference(new Object(), queue);
- System.out.println(reference);
- System.gc();
- Reference reference1 = queue.remove();
- System.out.println(reference1);
源码分析
Reference 和 ReferenceQueue
Reference 内部有几个比较重要的属性
- // 用于保存对象的引用,GC会根据不同Reference来特别对待
- private T referent;
- // 如果需要通知机制,则保存的对对应的队列
- ReferenceQueue < ?super T > queue;
- /* 这个用于实现一个单向循环链表,用以将保存需要由ReferenceHandler处理的引用 */
- Reference next;
- static private class Lock {};
- // 锁,用于同步pending队列的进队和出队
- private static Lock lock = new Lock();
- // 此属性保存一个PENDING的队列,配合上述next一起使用
- private static Reference pending = null;
状态图
内部类 ReferenceHandler
ReferenceHandler 作为 Reference 的静态内部类,用于实现将 pending 队列里面的 Reference 实例依次添加到不同的 ReferenceQueue 中(取决于 Reference 里面的 queue)。该 pending 的元素由 GC 负责加入。
注:这里对 pending 队列进行加锁,个人认为是因为 GC 线程可能和 ReferenceHandler 所在的线程并发执行,如 GC 采用 CMS 并发收集的时候。
如下代码所示
- // 此线程在静态块中启动,即一旦使用了Reference,则会启动该线程
- private static class ReferenceHandler extends Thread {
- public void run() {
- for (;;) {
- Reference r;
- synchronized(lock) {
- if (pending != null) {
- r = pending;
- Reference rn = r.next;
- // 从pending中取下一个元素,如果后继为空,则next指向自身 pending = (rn == r) ? null : rn;
- r.next = r;
- } else {
- try {
- // 没有则等待,后续加入元素会调用lock.notify唤醒
- lock.wait();
- } catch(InterruptedException x) {}
- continue;
- }
- }
- // ...
- ReferenceQueue q = r.queue;
- // 如果该Reference注册了对应的Queue,则加入到该Queue中
- if (q != ReferenceQueue.NULL) q.enqueue(r);
- }
- }
- }
ReferenceQueue 属性
- // 用于标识没有注册Queue
- static ReferenceQueue NULL = new Null();
- // 用于标识已经处于对应的Queue中
- static ReferenceQueue ENQUEUED = new Null();
- static private class Lock {};
- /* 互斥锁,用于同步ReferenceHandler的enqueue和用户线程操作的remove和poll出队操作 */
- private Lock lock = new Lock();
- // 队列
- private volatile Reference < ?extends T > head = null;
- // 队列中的元素个数
- private long queueLength = 0;
ReferenceQueue.enqueue
只会通过 Reference 里要调用该方法,用于将 Reference 放入到当前队列中
- boolean enqueue(Reference < ?extends T > r) {
- synchronized(r) {
- // 判断是否已经入队了
- if (r.queue == ENQUEUED) return false;
- synchronized(lock) {
- r.queue = ENQUEUED;
- // 单向循环
- r.next = (head == null) ? r: head;
- head = r;
- queueLength++;
- if (r instanceof FinalReference) {
- sun.misc.VM.addFinalRefCount(1);
- }
- // 通知当前挂起的线程(调用remove时有可能会挂起)
- lock.notifyAll();
- return true;
- }
- }
- }
ReferenceQueue.remove
- public Reference < ?extends T > remove(long timeout) throws IllegalArgumentException,
- InterruptedException {
- if (timeout < 0) {
- throw new IllegalArgumentException("Negative timeout value");
- }
- synchronized(lock) {
- // 从队列中取出一个元素
- Reference < ?extends T > r = reallyPoll();
- // 如果不为空,则直接返回
- if (r != null) return r;
- for (;;) {
- // 否则等待,由enqueue时notify唤醒
- lock.wait(timeout);
- r = reallyPoll();
- if (r != null) return r;
- if (timeout != 0) return null;
- }
- }
- }
具体执行流程
以上述示例 Demo1 作为分析
- // 创建一个引用队列
- ReferenceQueue queue = new ReferenceQueue();
- // 创建虚引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null
- WeakReference reference = new WeakReference(new Object(), queue);
- System.out.println(reference);
- // 当GC执行后,由于是虚引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING
- System.gc();
- /* ReferenceHandler从pending中取下该元素,并且将该元素放入到queue中,此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED */
- /* 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL */
- Reference reference1 = queue.remove();
- System.out.println(reference1);
应用 -WeakHashMap
WeakHashMap 在使用上和 HashMap 类型,都是 Hash + 链表解决冲突,唯一不同点在于前者的 Key 是使用虚引用来实现的,即当进行垃圾回收的时候,就是被回收掉,此时 WeakHashMap 会在下次操作的时候,根据被回收掉的 Key,从 Map 里面移除掉。
Entry
当创建 Entry 的时候,会注册进当前 Map 属性的 queue,当 key 被回收后,则该 Entry 会被放入到 queue 中,每当操作 Map 的时候,才会将原有的 Value 清除掉。(由 expungeStaleEntries 方法来进行,并且没有启动一个单独的线程来处理,没有必要,这样子简化了逻辑以及避免锁的开销)
- // 外部WeakHashMap属性
- private final ReferenceQueue < Object > queue = new ReferenceQueue < >();
- /* 这里采用了集成WeakReference而不是直接使用,是因为当被回收的时候,具体的Key是不知道的,这里需要往WeakReference额外加入一些属性,以便在被回收后通知时,能够定位到具体的Key/value */
- private static class Entry < K,
- V > extends WeakReference < Object > implements Map.Entry < K,
- V > {
- // 这里属性不能加入key,否则会导致存在强引用而不能被视为WeakReference回收掉
- V value;
- int hash;
- Entry < K,
- V > next;
- Entry(Object key, V value, ReferenceQueue < Object > queue, int hash, Entry < K, V > next) {
- super(key, queue);
- this.value = value;
- this.hash = hash;
- this.next = next;
- }
- // ...
- }
来源: http://www.phperz.com/article/17/1222/357896.html