重識java-WeakHashMap

四種引用

  • 強引用(StrongReference)
    • 強引用是使用最廣泛的引用,平時咱們常寫的A a = new A();就是強引用
    • GC不會回收強引用,即便內存不足的狀況下也不會,寧肯OutOfMemeryError
  • 軟引用(SoftReference)
    • SoftReference的主要特色是具備較強的引用功能。
    • 只有當內存不夠的時候才進行回收,而在內存足夠的時候,一般不被回收。
    • 另外,引用對象還能保證在 Java 拋出 OutOfMemoryError 以前,被設置爲null。
    • 軟引用的使用能夠參考guava-cache
  • 弱引用(WeakReference)
    • WeakReference 在垃圾回收器運行時,必定會被回收,而不像 SoftReference 須要條件。可是,若對象的引用關係複雜,則可能須要屢次回收才能達到目的。
  • 虛引用(PhantomReference)
    • PhantomReference主 要 用 於 輔 助finalize 方法。
    • PhantomReference 對象執行完了 finalize 方法後,成爲 Unreachable Objects。但還未被回收,在此時,能夠輔助 finalize 進行一些後期的回收工做。

弱引用的實現

put函數數據結構

public V put(K key, V value) {
	//若是key是null,則使用定義的常量NULL_KEY代替null。
    Object k = maskNull(key);
    int h = hash(k);
    Entry<K,V>[] tab = getTable();
    int i = indexFor(h, tab.length);

    for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
	    //若是原來有這個key,就替換並返回舊value
        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, value, queue, h, e);
    if (++size >= threshold)
        resize(tab.length * 2);
    return null;
}

WeakHashMapEntry實現,繼承了WeakReference,每個Entry都有其屬於的ReferenceQueue,使得後面JVM在垃圾回收後,查找Reference對象組件pending鏈,並將Entry移動到ReferenceQueue成爲可能。函數

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

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

    @SuppressWarnings("unchecked")
    public K getKey() {
        return (K) WeakHashMap.unmaskNull(get());
    }

    public V getValue() {
        return value;
    }

    public V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        K k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            V v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public int hashCode() {
        K k = getKey();
        V v = getValue();
        return Objects.hashCode(k) ^ Objects.hashCode(v);
    }

    public String toString() {
        return getKey() + "=" + getValue();
    }
}

Entry的構造函數中調用super(key, queue); 將Key處理成Reference:this

Reference(T referent, ReferenceQueue<? super T> queue) {
	this.referent = referent;
	this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

HashMap比較一下,Entry不直接引用Key這個對象,而是將引用關係放到了父類WeakReference中,能夠看出WeakHashMap將傳入的key包裝成了WeakReference,並傳入了一個ReferenceQueue;可是弱引用的實現細節仍是不清楚......線程

key如何清理

static private class Lock { };
private static Lock lock = new Lock();

//pending是一個鏈表結構
private static Reference<Object> pending = null;

static {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent());
    Thread handler = new ReferenceHandler(tg, "Reference Handler");
    /* If there were a special system-only priority greater than
     * MAX_PRIORITY, it would be used here
     */
    handler.setPriority(Thread.MAX_PRIORITY);
    handler.setDaemon(true);
    handler.start();
}

線程的優先級設成MAX,是一個什麼樣的線程須要如此高的權限?pending 、lock 都被static聲明,lock.wait以後誰來喚醒,互聯網上一頓蒐羅,才明白JVM參與了這些事。 用通俗的話把JVM乾的事串一下: 假設,WeakHashMap對象裏面已經保存了不少對象的引用。JVM使用進行CMS GC垃圾回收的時候,會建立一個ConcurrentMarkSweepThread(簡稱CMST)線程去進行垃圾回收,ConcurrentMarkSweepThread線程被建立的同時會建立一個SurrogateLockerThread(簡稱SLT)線程而且啓動它,SLT啓動以後,處於等待階段。 CMST開始垃圾回收時,會發一個消息給SLT讓它去獲取Java層Reference對象的全局鎖:lock。 直到CMS GC完畢以後,JVM會將WeakHashMap中全部被回收的對象所屬的WeakReference容器對象放入到Referencepending 屬性當中(每次GC完畢以後,pending屬性基本上都不會爲null了),而後通知SLT釋放而且notify全局鎖: lock。此時激活了ReferenceHandler線程的run方法,使其脫離wait狀態,開始工做了。ReferenceHandler這個線程會將pending中的全部WeakReference對象都移動到它們各自的列隊當中,好比當前這個WeakReference屬於某個WeakHashMap對象,那麼它就會被放入相應的ReferenceQueue列隊裏面(該列隊是鏈表結構)。code

Gc完成後, pending賦值,lock釋放,此時ReferenceHandler 獲取lock鎖,將 pending 中的Reference對象壓入了各自的 ReferenceQueue對象

pendingReference對象,給JVM使用的數據結構。pending.discovered返回下一個Reference對象。繼承

// MAX_PRIORITY線程將pending的references所有入列
private static class ReferenceHandler extends Thread {

    ReferenceHandler(ThreadGroup g, String name) {
        super(g, name);
    }

    public void run() {
        for (;;) {
            Reference<Object> r;
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    try {
                        try {
                            lock.wait();
                        } catch (OutOfMemoryError x) { }
                    } catch (InterruptedException x) { }
                    continue;
                }
            }

            // Fast path for cleaners
            if (r instanceof Cleaner) {
                ((Cleaner)r).clean();
                continue;
            }

            ReferenceQueue<Object> q = r.queue;
            if (q != ReferenceQueue.NULL) q.enqueue(r);
        }
    }
}

Entry清理

當GC以後,WeakHashMap對象裏面get、put數據或者調用size方法的時候,WeakHashMapHashMap多了一個 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;
            }
        }
    }
}

expungeStaleEntries方法 就是將ReferenceQueue列隊中的WeakReference依依poll出來去和Entry[]數據作比較,若是發現相同的,則說明這個Entry所保存的對象已經被GC掉了,那麼將Entry[]內的Entry對象剔除掉,這樣就把被GC掉的 WeakReference對應的EntryWeakHashMap中移除了。ci

Thanks for reading! want moreget

相關文章
相關標籤/搜索