WeakHashMap<K,V> 中的弱引用

相信很多人对WeakHashMap并没有完全理解。

WeakHashMap 持有的弱引用的 Key。

1. 弱引用的概念:

  弱引用是用来描述非必需对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

2. WeakHashMap中的弱引用

Key是如何被清除的?

  WeakHashMap中的清除Key的核心方法:

private void expungeStaleEntries() {
    Entry<K,V> e;
    while ( (e = (Entry<K,V>) queue.poll()) != null) {
        int h = e.hash;
        int i = indexFor(h, table.length);

        Entry<K,V> prev = table[i];
        Entry<K,V> p = prev;
        while (p != null) {
            Entry<K,V> next = p.next;
            if (p == e) {
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.next = null;  // Help GC
                e.value = null; //  "   "
                size--;
                break;
            }
            prev = p;
            p = next;
        }
    }
}

 

  查看调用关系,可以看到几乎所有的方法都直接或间接的调用了该方法。但是查看WeakHashMap源码,并没有找到何时将Entry放入queue中。

  那么queue队列中的Entry是如何来的?

 

Key弱引用是如何关联的?

  毫无疑问,一定是在put元素的时候,key被设置为弱引用。    

    public V put(K key, V value) {
        K k = (K) maskNull(key);
        int h = HashMap.hash(k.hashCode());
        Entry[] tab = getTable();
        int i = indexFor(h, tab.length);

        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
            if (h == e.hash && eq(k, e.get())) {
                V oldValue = e.value;
                if (value != oldValue)
                    e.value = value;
                return oldValue;
            }
        }
        modCount++;
        Entry<K,V> e = tab[i];
        tab[i] = new Entry<K,V>(k, value, queue, h, e); //创建一个新节点
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }

 

  其中的queue为:

    /**
     * Reference queue for cleared WeakEntries
     */
    private final ReferenceQueue<K> queue = new ReferenceQueue<K>();

        再来看一下Entry<K,V>的声明及构造函数:

    private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {/**
         * Creates new entry.
         */
        Entry(K key, V value,
          ReferenceQueue<K> queue,
              int hash, Entry<K,V> next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
    }

  Entry<K,V>继承了WeakReference,并且在构造函数中将key 和 queue 提交给WeakReference,那么再来看一下WeakReference的构造函数:

public class WeakReference<T> extends Reference<T> {
  //....
public WeakReference(T referent, ReferenceQueue<? super T> q) {   super(referent, q); } }
public abstract class Reference<T> {

  private static Reference pending = null;
  Reference(T referent, ReferenceQueue<? super T> queue) {
      this.referent = referent;
      this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
  }
}

  现在答案就在Reference中。

       打开Reference源码,可以看到一个静态块:

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

  其中for循环直到 获取到 JVM 线程组,使用JVM线程执行ReferenceHandler。

  ReferenceHandler是Reference的内部类:

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

  现在我们应该已经清楚了,守护线程一直执行入队操作,将key关联的Entry<K,V>放入queue中。

  但是将key放入queue中需要前提条件: pending

  这个pending是在垃圾回收的时候,JVM计算对象key的可达性后,发现没有该key对象的引用,那么就会把该对象关联的Entry<K,V>添加到pending中,

所以每次垃圾回收时发现弱引用对象没有被引用时,就会将该对象放入待清除队列中,最后由应用程序来完成清除,WeakHashMap中就负责由

方法expungeStaleEntries()来完成清除。

例子: 

   @Test
    public void weakHashMap(){
        Map<String, String> weak = new WeakHashMap<String, String>();
        weak.put(new String("1"), "1");
        weak.put(new String("2"), "2");
        weak.put(new String("3"), "3");
        weak.put(new String("4"), "4");
        weak.put(new String("5"), "5");
        weak.put(new String("6"), "6");
        System.out.println(weak.size());
        System.gc();  //手动触发 Full GC
        try {
            Thread.sleep(50); //我的测试中发现必须sleep一下才能看到不一样的结果
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(weak.size());
    }

  上面的例子是可以正确的,但是下面的就有问题了:

   @Test
    public void weakHashMap(){
        Map<String, String> weak = new WeakHashMap<String, String>();
        weak.put("1", "1");
        weak.put("2", "2");
        weak.put("3", "3");
        weak.put("4", "4");
        weak.put("5", "5");
        weak.put("6", "6");
        System.out.println(weak.size());
        System.gc();
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(weak.size());
    }

  无论sleep多长时间,引用也不会被清除。这涉及到String在JVM中的工作方式了,这个问题留给读者自己思考。

 

   

posted @ 2017-07-12 13:08  WanderingAlbatross  阅读(1328)  评论(0编辑  收藏  举报