浅析Java Reference

  Java1.2引入了新的概念——Reference,在这之前都是默认的强引用,即Strong Reference。在GC过程中,只要从GC Roots通过强引用有路径可达则说明接下来的程序还可能用到,就不能回收,反之则回收。
  但是这种方式比较死板,为了增加垃圾回收的灵活性便有了java.lang.ref类库,里头包含最重要的抽象类Reference,及其三个继承类:SoftReference(软引用)、WeakReference(弱引用)和PhantomReference(幻影引用)。当垃圾回收器正在考察的对象只能通过上述三个中某个Reference对象才可获得时,这三个Reference派生类会为GC提供不同的指示:

  1. 当JVM报告内存不足的时候,所有只被SoftReference所指向的对象会被GC回收,否则不会回收;
  2. 当GC发现一个对象只能通过弱引用对象可达,将会释放WeakReference所引用的对象。
  3. 当GC发现一个对象只能通过幻影引用对象可达,将会将PhantomReference对象插入与其关联的ReferenceQueue队列中(PhantomReference对象的初始化必须有一个ReferenceQueue队列)。而此时PhantomReference所指向的对象只是执行了finalize方法,但是对象本身并没有被GC回收,要等到ReferenceQueue被真正的处理(如调用remove方法)后才会被回收。

那么上述功能是如何完成的呢?下面借助JDK源码简单分析下:
  在Reference类中有个域referent用来保存所指对象的引用:

private T referent;

/* -- Constructors -- */

Reference(T referent) {
    this(referent, null);
}

Reference(T referent, ReferenceQueuesuper T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

在初始化Reference对象时,referent会指向传递进来的对象。在GC时如果发现目标对象只有Reference对象中的referent指向时,就会根据上述三条规则执行回收策略。我们看到第二个构造函数另外包括一个ReferenceQueue对象,我们很多后续操作都需要依靠这个对象完成,那这个对象是干嘛用的呢?
  GC回收Reference所指对象后(这里实际指referent所指向的对象,另外PhantomReference是在回收前,finalize后),将Reference添加到与其绑定的ReferenceQueue中,程序通过调用ReferenceQueue的remove方法或poll方法来感知reference被GC回收的事件。下面是一段Reference的源码:

static private class Lock { };
private static Lock lock = new Lock();

/* List of References waiting to be enqueued.  The collector adds
* References to this list, while the Reference-handler thread removes
* them.  This list is protected by the above lock object.
*/
 private static Reference pending = null;

 /* High-priority thread to enqueue pending References
  */
 private static class ReferenceHandler extends Thread {

     ReferenceHandler(ThreadGroup g, String name) {
         super(g, name);
     }

     public void run() {
         for (;;) {

             Reference r;
             synchronized (lock) {
                 if (pending != null) {
                     r = pending;
                     Reference rn = r.next;
                     pending = (rn == r) ? null : rn;
                     r.next = r;
                 } else {
                     try {
                         lock.wait();
                     } catch (InterruptedException x) { }
                     continue;
                 }
             }

             // Fast path for cleaners
             if (r instanceof Cleaner) {
                 ((Cleaner)r).clean();
                 continue;
             }

             ReferenceQueue q = r.queue;
             if (q != ReferenceQueue.NULL) q.enqueue(r);
         }
     }
 }

 static {
     ThreadGroup tg = Thread.currentThread().getThreadGroup();
     for (ThreadGroup tgn = tg;
          tgn != null;
          tg = tgn, tgn = tg.getParent());
     Thread handler = new ReferenceHandler(tg, "Reference Handler");
     /* If there were a special system-only priority greater than
      * MAX_PRIORITY, it would be used here
      */
     handler.setPriority(Thread.MAX_PRIORITY);
     handler.setDaemon(true);
     handler.start();
 }

ReferenceHandler这个线程在Reference类初始化的时候就作为守护进程启动。注意这里有一个Reference类型的成员变量pending,ReferenceHandler会循环检测pending是否为空,如果为空就挂起等待。当Reference所指向的对象在GC阶段被回收,就会被添加到pending上,然后唤醒ReferenceHandler,将Reference加入ReferenceQueue中。现在就可以利用ReferenceQueue做一些后续操作了,就拿经典的WeakHashMap来说一说,先看看它的Entry:

 private final ReferenceQueue queue = new ReferenceQueue<>();

/**
 * The entries in this hash table extend WeakReference, using its main ref
 * field as the key.
 */
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    int hash;
    Entry next;

    Entry(Object key, V value,
          ReferenceQueue queue,
          int hash, Entry next) {
        super(key, queue);  //用key来初始化referent
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }
    //省略
} 
  

Entry继承了WeakReference,所以它有了弱引用的性质。这里让弱引用指向key值所指的对象,所以当没有其他更强的引用指向key所指对象时,目标对象会被GC掉,并将持有这个key的Entry加入queue所指的ReferenceQueue中去。既然key值都冇得了,自然这个Entry就没有存在的意义了,所以得找个机会把它咔嚓掉。WeakHashMap把这个时间点设置到了每一次调用getTable或size方法时,首先进行一次清扫工作,而这个清扫工作正是依赖于引用队列queue中记录的引用对象。

你可能感兴趣的:(Java)