WeakHashMap

前导知识:

强,软,弱,虚引用区别?

强引用是使用最普遍的引用:Object o=new Object(); 即使发生OOM也不会被GC

将对象的引用显示地置为null:o=null; // 帮助垃圾收集器回收此对象

 
软引用用来描述一些还有用但是并非必须的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存。
 String str=new String("abc");                                     // 强引用
 SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用
 
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。java中用WeakReference来实现弱引用。
 
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。 比如ThreadLocal中Entry对象的key就是弱引用。
 
虚引用也称为幻影引用:一个对象是都有虚引用的存在都不会对生存时间都构成影响,也无法通过虚引用来获取对一个对象的真实引用。唯一的用处:能在对象被GC时收到系统通知,JAVA中用PhantomReference来实现虚引用。
软(弱、虚)引用必须和一个引用队列(ReferenceQueue)一起使用,当gc回收这个软(弱、虚)引用引用的对象时,会把这个软(弱、虚)引用放到这个引用队列中。
虚引用用来标识对象的生命周期,在其指向的对象从内存中移除掉之后才会加入到引用队列。
 
WeakHashMap特性:
WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉,下一次当我们操作map的时候会把对应的Entry整个删除掉,基于这种特性,WeakHashMap特别适用于缓存处理。

它的扩容:

private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
    // 遍历旧桶
    for (int j = 0; j < src.length; ++j) {
        Entry<K,V> e = src[j];
        src[j] = null;
        while (e != null) {
            Entry<K,V> next = e.next;//保存它的后面的entry
            Object key = e.get();
            // 如果key等于了null就清除,说明key被gc清理掉了,则把整个Entry清除
            if (key == null) {
                e.next = null;  // Help GC
                e.value = null; //  "   "
                size--;
            } else {
                // 否则就计算在新桶中的位置并把这个元素放在新桶对应链表的头部
                int i = indexFor(e.hash, dest.length);
                e.next = dest[i];
                dest[i] = e;
            }
            e = next;
        }
    }
}

(1)判断旧容量是否达到最大容量;

(2)新建新桶并把元素全部转移到新桶中;

(3)如果转移后元素个数不到扩容门槛的一半,则把元素再转移回旧桶,继续使用旧桶,说明不需要扩容;

(4)否则使用新桶,并计算新的扩容门槛;

(5)转移元素的过程中会把key为null的元素清除掉,所以size会变小;主要记住这个删除元素的过程

expungeStaleEntries()方法

private void expungeStaleEntries() {
    // 遍历引用队列
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, 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;
                    // Must not null out e.next;
                    // stale entries may be in use by a HashIterator
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

(1)当key失效的时候gc会自动把对应的Entry添加到这个引用队列中;

(2)所有对map的操作都会直接或间接地调用到这个方法先移除失效的Entry,比如getTable()、size()、resize();

(3)这个方法的目的就是遍历引用队列,并把其中保存的Entry从map中移除掉,具体的过程请看类注释;

(4)从这里可以看到移除Entry的同时把value也一并置为null帮助gc清理元素,防御性编程。

测试使用:

public class WeakHashMapTest {

public static void main(String[] args) {
    Map<String, Integer> map = new WeakHashMap<>(3);

    // 放入3个new String()声明的字符串
    map.put(new String("1"), 1);
    map.put(new String("2"), 2);
    map.put(new String("3"), 3);

    // 放入不用new String()声明的字符串
    map.put("6", 6);

    // 使用key强引用"3"这个字符串
    String key = null;
    for (String s : map.keySet()) {
        // 这个"3"和new String("3")不是一个引用
        if (s.equals("3")) {
            key = s;
        }
    }

    // 输出{6=6, 1=1, 2=2, 3=3},未gc所有key都可以打印出来
    System.out.println(map);

    // gc一下
    System.gc();

    // 放一个new String()声明的字符串
    map.put(new String("4"), 4);

    // 输出{4=4, 6=6, 3=3},gc后放入的值和强引用的key可以打印出来
    System.out.println(map);

    // key与"3"的引用断裂
    key = null;

    // gc一下
    System.gc();

    // 输出{6=6},gc后强引用的key可以打印出来
    System.out.println(map);
}
}

在这里通过new String()声明的变量才是弱引用,使用”6″这种声明方式会一直存在于常量池中,不会被清理,所以”6″这个元素会一直在map里面,其它的元素随着gc都会被清理掉。

参考:

http://cmsblogs.com/?p=4735

posted @ 2019-06-10 22:52  LeeJuly  阅读(170)  评论(0)    收藏  举报