zoukankan      html  css  js  c++  java
  • Java Reference 源码分析

    @(Java)[Reference]

    Java Reference 源码分析

      Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互。即可以使用Reference对象来引用其它对象,但是最后还是会被垃圾收集器回收。程序有时候也需要在对象回收后被通知,以告知对象的可达性发生变更。
      Java提供了四种不同类型的引用,引用级别从高到低分别为FinalReference,SoftReference,WeakReference,PhantomReference。其中FinalReference不对外提供使用。每种类型对应着不同级别的可达性。

    简介

    强引用FinalReference

    强引用指的是,程序中有直接可达的引用,而不需要通过任何引用对象,如Object obj = new Object();中,obj为强引用。

    软引用SoftReference

    软引用,非强引用,但是可以通过软引用对象来访问。软引用的对象,只有在内存不足的时候(抛出OOM异常前),垃圾收集器会决定回收该软引用所指向的对象。软引用通常用于实现内存敏感的缓存。

    SoftReference<Object> softRef = new SoftReference<Object>(new Object());
    

    弱引用WeakReference

    弱引用,非强引用和软引用,但是可以通过弱引用对象来访问。弱引用的对象,不管内存是否足够,只要被垃圾收集器发现,该引用的对象就会被回收。实际的应用见WeakHashMap等。

    WeakReference<Object> weakRef = new WeakReference<Object>(new Object());
    

    虚引用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);
    

    源码分析

    ReferenceReferenceQueue

    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://docs.oracle.com/javase/7/docs/api/java/lang/ref/package-summary.html#package_description
    博客:
    http://www.importnew.com/20468.html
    http://hongjiang.info/java-referencequeue/
    http://blog.csdn.net/lyfi01/article/details/6415726

  • 相关阅读:
    八数码难题 (codevs 1225)题解
    小木棍 (codevs 3498)题解
    sliding windows (poj 2823) 题解
    集合删数 (vijos 1545) 题解
    合并果子 (codevs 1063) 题解
    等价表达式 (codevs 1107)题解
    生理周期 (poj 1006) 题解
    区间 (vijos 1439) 题解
    区间覆盖问题 题解
    种树 (codevs 1653) 题解
  • 原文地址:https://www.cnblogs.com/jabnih/p/6580665.html
Copyright © 2011-2022 走看看