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;
...
}
WeakHashMap 和 ThreadLocalMap 中 Entry 的 key 使用的都是弱引用,如果 key 沒(méi)有被強(qiáng)引用,那么在發(fā)生 GC 的情況下,key 對(duì)象會(huì)被回收。所以弱引用通常用來(lái)指向使用周期短暫的數(shù)據(jù),以提高內(nèi)存的使用效率。
key 被回收后,WeakHashMap 和 ThreadLocalMap 中就存在 key 為 null 的 entry,通常稱之為 stale entry。Entry 中的 value 也無(wú)法被使用,如果被不能清理就會(huì)發(fā)生內(nèi)存泄漏。
在 WeakHashMap 和 ThreadLocalMap 中,對(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)存泄漏。