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