threadlocal里面使用了一個存在弱引用的map,當釋放掉threadlocal的強引用以后,map里面的value卻沒有被回收.而這塊value永遠不會被訪問到了. 所以存在著內(nèi)存泄露.
** 最好的做法是將調(diào)用threadlocal的remove方法.**: 把當前ThreadLocal從當前線程的ThreadLocalMap中移除慨代。(包括key俄占,value)
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();// 將entry的對threadlocal的引用賦值為null
expungeStaleEntry(i);//將 entry的value賦值為null
return;
}
}
}
在threadlocal的生命周期中,都存在這些引用. 看下圖: 實線代表強引用,虛線代表弱引用
圖中荣病,ThreadLocalMap維護一個Entry的數(shù)組,所以一個線程可以有過個ThreadLocal實例开仰。
每個thread中都存在一個map(ThreadLocalMap), map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置為null以后,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強引用. 只有當前thread結(jié)束以后, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.
內(nèi)存泄漏的情況
所以得出一個結(jié)論就是只要這個線程對象被gc回收,就不會出現(xiàn)內(nèi)存泄露熬甫,但在threadLocal設(shè)為null和線程結(jié)束這段時間不會被回收的唉锌,就發(fā)生了我們認為的內(nèi)存泄露。其實這是一個對概念理解的不一致警儒,也沒什么好爭論的训裆。最要命的是線程對象不被回收的情況,這就發(fā)生了真正意義上的內(nèi)存泄露蜀铲。比如使用線程池的時候边琉,線程結(jié)束是不會銷毀的,會再次使用的记劝。就可能出現(xiàn)內(nèi)存泄露.
PS
Java為了最小化減少內(nèi)存泄露的可能性和影響变姨,在ThreadLocal的get,set的時候都會清除線程Map里所有key為null的value(get 方法會在遍歷的時候如果遇到key為null,就調(diào)用expungeStaleEntry方法擦除厌丑,set方法在遍歷的時候定欧,如果遇到key為null,就調(diào)用replaceStaleEntry方法替換掉怒竿。見下面代碼)砍鸠。
所以最怕的情況就是,threadLocal對象設(shè)null了愧口,開始發(fā)生“內(nèi)存泄露”睦番,然后使用線程池,這個線程結(jié)束耍属,線程放回線程池中不銷毀托嚣,這個線程一直不被使用,或者分配使用了又不再調(diào)用get,set方法厚骗,那么這個期間就會發(fā)生真正的內(nèi)存泄露示启。
java.lang.ThreadLocal.ThreadLocalMap#set
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;
}
}
java.lang.ThreadLocal.ThreadLocalMap#getEntry--->getEntryAfterMiss
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
//注意這里
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;