Java的弱引用—WeakHashMap

Posted 十木禾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java的弱引用—WeakHashMap相关的知识,希望对你有一定的参考价值。

在《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 于上海

以上是关于Java的弱引用—WeakHashMap的主要内容,如果未能解决你的问题,请参考以下文章

JAVA中的四种引用以及ReferenceQueue和WeakHashMap的使用示例

HashMap 之弱引用 - WeakHashMap

搞定 WeakHashMap 的工作原理一篇文章就够了!!!

集合总结:WeakHashMap + 强/软/弱/虚引用

什么是WeakHashMap--转

理解Java中的弱引用(Weak Reference)