ThreadLocal是一種confinement,confinement和local及immutable都是線程安全的(如果JVM可信的話)。因?yàn)閷?duì)每個(gè)線程和value之間存在hash表,而線程數(shù)量未知,從表象來看ThreadLocal會(huì)存在內(nèi)存泄露,讀了代碼,發(fā)現(xiàn)實(shí)際上也可能會(huì)內(nèi)存泄露。
事實(shí)上每個(gè)Thread實(shí)例都具備一個(gè)ThreadLocal的map,以ThreadLocal Instance為key,以綁定的Object為Value。而這個(gè)map不是普通的map,它是在ThreadLocal中定義的,它和普通map的最大區(qū)別就是它的Entry是針對(duì)ThreadLocal弱引用的,即當(dāng)外部ThreadLocal引用為空時(shí),map就可以把ThreadLocal交給GC回收,從而得到一個(gè)null的key。
這個(gè)threadlocal內(nèi)部的map在Thread實(shí)例內(nèi)部維護(hù)了ThreadLocal Instance和bind value之間的關(guān)系,這個(gè)map有threshold,當(dāng)超過threshold時(shí),map會(huì)首先檢查內(nèi)部的ThreadLocal(前文說過,map是弱引用可以釋放)是否為null,如果存在null,那么釋放引用給gc,這樣保留了位置給新的線程。如果不存在slate threadlocal,那么double threshold。除此之外,還有兩個(gè)機(jī)會(huì)釋放掉已經(jīng)廢棄的threadlocal占用的內(nèi)存,一是當(dāng)hash算法得到的table index剛好是一個(gè)null key的threadlocal時(shí),直接用新的threadlocal替換掉已經(jīng)廢棄的。另外每次在map中新建一個(gè)entry時(shí)(即沒有和用過的或未清理的entry命中時(shí)),會(huì)調(diào)用cleanSomeSlots來遍歷清理空間。此外,當(dāng)Thread本身銷毀時(shí),這個(gè)map也一定被銷毀了(map在Thread之內(nèi)),這樣內(nèi)部所有綁定到該線程的ThreadLocal的Object Value因?yàn)闆]有引用繼續(xù)保持,所以被銷毀。
從上可以看出Java已經(jīng)充分考慮了時(shí)間和空間的權(quán)衡,但是因?yàn)橹脼閚ull的threadlocal對(duì)應(yīng)的Object Value無法及時(shí)回收。map只有到達(dá)threshold時(shí)或添加entry時(shí)才做檢查,不似gc是定時(shí)檢查,不過我們可以手工輪詢檢查,顯式調(diào)用map的remove方法,及時(shí)的清理廢棄的threadlocal內(nèi)存。需要說明的是,只要不往不用的threadlocal中放入大量數(shù)據(jù),問題不大,畢竟還有回收的機(jī)制。
綜上,廢棄threadlocal占用的內(nèi)存會(huì)在3中情況下清理:
1 thread結(jié)束,那么與之相關(guān)的threadlocal value會(huì)被清理
2 GC后,thread.threadlocals(map) threshold超過最大值時(shí),會(huì)清理
3 GC后,thread.threadlocals(map) 添加新的Entry時(shí),hash算法沒有命中既有Entry時(shí),會(huì)清理
那么何時(shí)會(huì)“內(nèi)存泄露”?當(dāng)Thread長(zhǎng)時(shí)間不結(jié)束,存在大量廢棄的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一個(gè)廢棄ThreadLocal在map中命中)時(shí)。
@2008 楊一. 版權(quán)所有. 保留所有權(quán)利