目錄
- ThreadLocal.set方法源碼分析
- ThreadLocalMap.set方法源碼分析
- 內存泄漏分析
set方法源碼分析
- 獲取當前線程的threadLocals(ThreadLocalMap)
- 如果ThreadLocalMap為空則創建,否則設置value
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
為什么threadLocals是Map結構?
要解答這個問題需要看一下createMap部分。沒錯,key是當前ThreadLocal實例,一個線程是可以由多個ThreadLocal實例的,所以使用map結構
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap.set方法源碼分析
根據key,value創建Entry,將entry放入key的hash值對應tab表對應下標處
Entry結構實現
可以看到entry的key是對ThreadLocal的弱引用。回憶下,弱引用會在下一次gc時被回收掉。引用API文檔中的解釋:https://docs.oracle.com/javase/7/docs/api/java/lang/ref/WeakReference.html
Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.
弱引用對象,弱引用并不會阻止它們引用指向的對象變得可終結、終結,然后被回收。弱引用通常用于實現規范化映射。
Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.
假定垃圾回收器在某刻確定對象是弱可達的。此時,它將原子性的清除弱可達對象的所有弱引用,以及所以其他弱可達對象的所有弱引用,從這些通過強引用和軟引用鏈可訪問到的弱可達對象。同時它將聲明所有以前的弱可達對象都是為可終結的。同時或以后某個時候它將新清除的弱引用壓入使用引用隊列注冊的弱引用的引用隊列中。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

內存泄漏分析
如果GC回收了Entry的key對ThreadLocal的弱引用。那么key為null,也就永遠不可能再訪問到50MB的value。此時便出現了泄漏。那么ThreadLocal既然使用了弱引用會不考慮該問題嗎?那么我們來繼續分析它的源碼
set部分
可以看到set值時,如果發現存在老的entry的key為null會觸發遍歷tab表清楚掉所有key為null的entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
get部分
get部分也會清楚entry的key為null的數據,但是前提是get的key未獲取到時:getEntryAfterMiss
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
總結
ThreadLocal內存泄漏須要滿足以下條件
- 線程沒有終結
- GC回收掉了弱引用
- 沒有再次調用ThreadLocal的set、get方法
- 沒有手工清除數據,即沒有調用remove方法
|