WeakReference 在 WeakHashMap 和 ThreadLocalMap 中的使用

WeakHashMap

static class Entry extends WeakReference<ThreadLocal<?>> {
           /** The value associated with this ThreadLocal. */
           Object value;

           Entry(ThreadLocal<?> k, Object v) {
               super(k);
               value = v;
           }
       }

TheadLocalMap

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

WeakHashMapThreadLocalMap 中 Entry 的 key 使用的都是弱引用,如果 key 沒(méi)有被強(qiáng)引用,那么在發(fā)生 GC 的情況下,key 對(duì)象會(huì)被回收。所以弱引用通常用來(lái)指向使用周期短暫的數(shù)據(jù),以提高內(nèi)存的使用效率。

key 被回收后,WeakHashMapThreadLocalMap 中就存在 key 為 null 的 entry,通常稱之為 stale entry。Entry 中的 value 也無(wú)法被使用,如果被不能清理就會(huì)發(fā)生內(nèi)存泄漏。

WeakHashMapThreadLocalMap 中,對(duì) stale entry 的清理方式是不同的。

WeakHashMap 使用 expungeStaleEntries() 方法來(lái)清理:

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 為 reference queue,用來(lái)存放需要被清理的 WeakEntries,即那些 stale entries。通過(guò)遍歷來(lái)移除 table 中的 stale entries。

expungeStaleEntries() 方法會(huì)在 table 獲取、擴(kuò)容等場(chǎng)景下調(diào)用。

在 LocalThreadMap 中也有相應(yīng)的 expungeStaleEntries() 方法:

/**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

expungeStaleEntries() 在擴(kuò)容rehash、set 等場(chǎng)景下會(huì)被調(diào)用,但是從源碼中可以發(fā)現(xiàn),expungeStaleEntries() 方法被調(diào)用的條件比較苛刻。因此,在使用 ThreadLocal 時(shí),通常需要我們顯示調(diào)用 ThreadLocal 的 remove() 方法來(lái)觸發(fā) expungeStaleEntries()。

當(dāng)使用線程池時(shí),因?yàn)榇嬖诰€程重用機(jī)制,所以在使用完ThreadLocal 后,需要調(diào)用其 remove() 方法來(lái)避免內(nèi)存泄漏。

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

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

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