WeakHashMap 解析

WeakHashMap


public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {

 ......
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
 ......

}

WeakHashMap和HashMap一樣key和value的值都可以為null,并且也是無序的。但是HashMap的null是存在table[0]中的,這是固定的,并且null的hash為0,而在WeakHashMap中的null卻沒有存入table[0]中。 這是因為WeakHashMap對null值進(jìn)行了包裝:NULL_KEY

Entry 代碼


    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }

    ......
    private void expungeStaleEntries() {
    
        // 遍歷 ReferenceQueue 的 WeakReference 對象 Key 已經(jīng)被GC回收的
        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;
                // 遍歷數(shù)組指定位置的單鏈表
                while (p != null) {
                    Entry<K,V> next = p.next;
                    // 找到與 queue 中 WeakReference 對象對應(yīng)的節(jié)點 
                    if (p == e) {
                        // 判斷 p 是不是單鏈表的首節(jié)點
                        if (prev == e)
// 刪除節(jié)點 -> 修改首節(jié)點為其后繼節(jié)點(數(shù)組中存放的是單鏈表的首節(jié)點)
                            table[i] = next;
                        else
                    // 刪除節(jié)點 -> 修改其前繼節(jié)點的后指針
                            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;
                }
            }
        }
    }

expungeStaleEntries調(diào)用關(guān)系

在 WeakHashMap 中幾乎所有的操作(put,remove,get,size)都會涉及到消除操作,這也正體現(xiàn)了它的 weak。

NULL_KEY

上面提到了 WeakHashMap 的成員變量 NULL_KEY,當(dāng) key = null 時,會被其代替。

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

key 是弱引用,在其被 GC 回收后,對應(yīng)的節(jié)點就會從(key-value)變成(null-value);
當(dāng)要新增一個 key = null 的節(jié)點時,即 put(null,key)為了避免其被當(dāng)成無用節(jié)點。null 會被
NULL_KEY 代替,變成(NULL_KEY,key)以此來區(qū)分。

實例


public class AnalyzeWeakHashMap {
    public static void main(String[] args) throws Exception {
//        test1();
//        test2();

//        test3();
//
        test4();


    }

    private static void test4() {
        List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();
        for (int i = 0;; i++) {
            WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
            d.put(new byte[10000][10000], new byte[10000][10000]);
            maps.add(d);
            System.gc();
            // 關(guān)鍵 -> 區(qū)別:
//            System.err.print(i+",");

            System.err.println(  "size = " + d.size());

            // 輸出結(jié)果:
            // size = 0
            // ...
        }
    }

    private static void test3() {
        List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();
        for (int i = 0;; i++) {
            WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
            d.put(new byte[10000][10000], new byte[10000][10000]);
            maps.add(d);
            System.gc();
            System.err.print(i+",");

            // 輸出結(jié)果:
            // 0,1,2,3,4,5,6,7,8,9,10,
            // Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at Test3.main(Test3.java:10)
        }
    }

    private static void test2() throws InterruptedException {
        WeakHashMap<Object, String> wmap = new WeakHashMap<Object, String>();
        wmap.put(new Object(), "a");
        System.gc();
        Thread.sleep(100);
        System.out.println(wmap.size());
        // 輸出結(jié)果:0
    }

    private static void test1() throws InterruptedException {
        WeakHashMap<Object, String> wmap = new WeakHashMap<Object, String>();
        wmap.put(null, "a");
        System.gc();
        Thread.sleep(100);
        System.out.println(wmap.size());
        // 輸出結(jié)果:1
    }

}

實例 三,在程序運行不久后就 OOF 了。實例 四 ,則能一直運行下去。觀察代碼,發(fā)現(xiàn)它們的區(qū)別僅僅是最后調(diào)用了 size 方法。原因就在這里,size 方法會調(diào)用 expungeStaleEntries 清除無用節(jié)點,防止內(nèi)存不斷增加。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容