• 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中的工作方式了,这个问题留给读者自己思考。

       

  • 相关阅读:
    Java--Filter(过滤器)
    TP5.1验证Token和Electron-vue头部携带Token
    TP5.1让验证码在另外的项目(Electron-vue)里面使用
    Electron-vue请求携带cookie跨域问题
    Electron-vue在发送请求时携带cookie
    TP5.1解决跨域
    Electron-vue解决跨域
    Electron-vue运行之后出现了文件浏览器
    Electron-vue取消代码检查Eslint
    使用Composer安装TP5.1出现zsh: no matches found: 5.1.*
  • 原文地址:https://www.cnblogs.com/selfchange/p/7154909.html
Copyright © 2020-2023  润新知