Java的弱引用—WeakHashMap

来源:互联网 发布:php use require 编辑:程序博客网 时间:2024/06/05 11:23

在《Effective Java》中的p23页有涉及到WeakHashMap的相关知识,在这篇文章中做一个总结以及介绍一下相关知识。

在这里我们分成三个部分来说明一下,这只是我自己参看JDK源码和上网搜索资料得到的结果,如有错误,欢迎指出,我不胜荣幸。


WeakHashMap和HashMap有什么不同

我们知道WeakHashMap是弱引用,而HashMap是强引用。
这就是说当我们给Java虚拟机分配的内存不足的时候,HashMap宁可抛出OutOfMemoryError异常也不会回收其相应的没有被引用的对象,而我们的WeakHashMap则会回收存储在其中但有被引用的对象。

如下的程序实例,运用的JVM参数为:-Xmx5m -Xms5m -XX:+PrintGC

HashMap的测试代码

    public static void main(String[] args) {        HashMap hashMap = new HashMap();        for (int i = 0; ; i++) {            hashMap.put(i, new String("HashMap"));//一直往里面加数据            if (i % 1000 == 0) { //每隔一千次判断一下有没有对象被回收                for (int j = 0; j < i; j++) {//遍历一遍                    if (hashMap.get(j) == null) {                        System.out.println("第" + j + "个对象开始回收");                        return;                    }                }            }        }    }

我们运行程序,发现没有任何对象被回收,最后抛出了异常

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    at java.util.HashMap.addEntry(HashMap.java:753)    at java.util.HashMap.put(HashMap.java:385)

那我们再来证明一下WeakHashMap会回收其中存储的没有被引用的对象

    public static void main(String[] args) {        WeakHashMap hashMap = new WeakHashMap();        for (int i = 0; ; i++) {            hashMap.put(i, new String("WeakHashMap"));//一直往里面加数据            if (i % 1000 == 0) { //每隔一千次判断一下有没有对象被回收                for (int j = 0; j < i; j++) {//遍历一遍                    if (hashMap.get(j) == null) {                        System.out.println("第" + j + "个对象开始回收");                        return;                    }                }            }        }    }

最后程序的输出如下

[GC 1408K->787K(4992K), 0.0067048 secs]第241个对象开始回收

可知WeakHashMap将其中存储的键为241的对象开始回收了。


我们接下来思考,WeakHashMap是怎样知道要回收对象的呢?

WeakHashMap通过将一些没有被引用的键的值赋值为null,这样的话就会告知GC去回收这些存储的值了。

那么也就是说,如果我们将其所有的键都添加引用,那么其就不会被回收了?我们来写一段代码测试一下

    public static void main(String[] args) {        WeakHashMap weakHashMap = new WeakHashMap();        HashMap hashMap=new HashMap();        for (int i = 0; ; i++) {            Integer num=new Integer(i);            hashMap.put(i,num);     // num 被引用            weakHashMap.put(num, new String("WeakHashMap"));//将num改为i就会有对象被回收            if (i % 1000 == 0) { //每隔一千次判断一下有没有对象被回收                for (int j = 0; j < i; j++) {//遍历一遍                    if (weakHashMap.get(j) == null) {                        System.out.println("第" + j + "个对象开始回收");                        return;                    }                }            }        }    }

我们在HashMap中存储了WeakHashMap的键,这样就会是的其不会被回收,最后测试结果表示没有对象被回收,程序也就像我们期待的一下报了OutOfMemoryError

当然可能会有人说,为什么这个OutOfMemoryError不是我们的HashMap引起的呢,然后WeakHashMap还没有达到回收的条件?

针对这一点,我根据程序中的注释将WeakHahMap的键从num改为i,最后会发现有对象被回收。


既然WeakHashMap是将我们的键值设置为null从而引起GC的,那么我们将null作为键存进去,为什么不会导致被回收呢?

这时候我们就需要看看其JDK的有关put方法的源码了

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

我们重点看一下开始的判断键时候为nullmaskNull()方法。

    private static Object maskNull(Object key) {        return (key == null ? NULL_KEY : key);    }

我们发现,如果keynull的话,返回的是NULL_KEY这个静态值,我们再来看一下这个值是什么的时候,就恍然大悟了

    /**     * Value representing null keys inside tables.     */    private static final Object NULL_KEY = new Object();

WeakHashMap在存储null为键的时候,其实存储的是其本身的静态成员变量Object,也就是说存储的不是null

这也就解释了为什么存储键为null不会被马上回收。


最后我们来看一下程序是在什么时候来判断要将没有引用的key标记为null的呢?

这时候WeakHashMap中的一个非常重要的方法expungeStaleEntries()就登场了。

WeakHashMapput()get()remove()等等方法中都调用了一个getTable()方法,而这个getTable()方法的源码如下:

    private Entry[] getTable() {        expungeStaleEntries();        return table;    }

可以知道他们其实调用的都是expungeStaleEntries()方法。
可知这个方法是一个非常重要的方法。

[注].出自: Java集合框架:WeakHashMap,一篇非常优秀的博客!

首先我们看一下其实现的源码

    /**     * Expunges stale entries from the table.     */    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;            }        }    }

可以看到每调用一次expungeStaleEntries()方法,就会在引用队列中寻找是否有将要被清除的key对象,如果有则在table中找到其值,并将value设置为nullnext指针也设置为null,让GC去回收这些资源。


2017-11-18 15:23 于上海