jdk原始碼分析之WeakHashMap

王世暉發表於2016-06-08

基本原理

WeakHashMap特點是,當除了自身有對key的引用外,此key沒有其他引用,那麼WeakHashMap會在下次對WeakHashMap進行增刪改查操作時及時丟棄該鍵值對,節約記憶體使用,此特性使得WeakHashMap非常適合構建快取系統。
WeakHashMap是主要通過expungeStaleEntries函式的來實現移除其內部不用的entry從而達到的自動釋放記憶體的目的。基本上只要對WeakHashMap的內容進行訪問就會呼叫expungeStaleEntries函式,從而達到清除不再被外部引用的key對應的entry鍵值對。如果預先生成了WeakHashMap,而在GC以前又不曾訪問該WeakHashMap,那麼因為沒有機會呼叫expungeStaleEntries函式,因此並不會回收不再被外部引用的key對應的entry。

Entry鍵值對

WeakHashMap的鍵值對Entry繼承自WeakReference,並實現了Map.Entry

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>

當弱引用指向的物件只能通過弱引用(沒有強引用或弱引用)訪問時,GC會清理掉該物件,之後,引用物件會被放到ReferenceQueue中。在Entry的建構函式中可以得知,通過super(key, queue)將key儲存為弱引用,通過this.value = value將value儲存為強引用。當key中的引用被gc掉之後,在下次訪問WeakHashMap(呼叫expungeStaleEntries函式)時相應的entry也會自動被移除。

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

expungeStaleEntries():清除過期的條目

從ReferenceQueue中取出過期的entry,從WeakHashMap找到對應的entry,逐一刪除

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

首先通過迴圈一直從queue中取過期entry直到取完為止

    for (Object x; (x = queue.poll()) != null; )

然後通過加鎖queue進行刪除過期entry的操作

    synchronized (queue) {
    ...
    }

在同步程式碼塊中先把從queue中取出的Object型別的資料強制轉化為Entry物件e,然後計算此entry在桶的位置(table陣列的下標i),然後開始遍歷entry連結串列,如果此entry是連結串列頭,設定此entry的後繼為新的連結串列頭

              if (prev == e)
                  table[i] = next;

否則將此entry的前序節點的後繼指標指向此entry的後繼節點

               else
                   prev.next = next;

最後設定被刪除的entry的value為null,加速垃圾回收,接著修改size

               e.value = null; // Help GC
               size--;

訪問WeakHashMap時,對過期條目進行清除

這裡寫圖片描述
這裡寫圖片描述
可以看到對WeakHashMap的增刪改查操作都會直接或者間接的呼叫expungeStaleEntries()方法,達到及時清除過期entry的目的。
此特性會到導致兩次呼叫size、get等方法可能會返回不一致的資料。

相關文章