前言
ThreadLocal
的作用是提供線程內(nèi)的局部變量纽哥,這種變量在線程的生命周期內(nèi)起作用弓摘,減少同一個線程內(nèi)多個函數(shù)或者組件之間一些公共變量的傳遞的復(fù)雜度。但是如果濫用 ThreadLocal
,就可能會導(dǎo)致內(nèi)存泄漏硼瓣。下面,我們將圍繞三個方面來分析 ThreadLocal
內(nèi)存泄漏的問題
-
ThreadLocal
實(shí)現(xiàn)原理 -
ThreadLocal
為什么會內(nèi)存泄漏 -
ThreadLocal
最佳實(shí)踐
ThreadLocal 實(shí)現(xiàn)原理
ThreadLocal
的實(shí)現(xiàn)是這樣的:每個Thread
維護(hù)一個 ThreadLocalMap
映射表置谦,這個映射表的 key
是 ThreadLocal
實(shí)例本身堂鲤,value
是真正需要存儲的 Object
。
也就是說 ThreadLocal
本身并不存儲值媒峡,它只是作為一個 key
來讓線程從 ThreadLocalMap
獲取 value
筑累。值得注意的是圖中的虛線,表示 ThreadLocalMap
是使用 ThreadLocal
的弱引用作為 Key
的丝蹭,弱引用的對象在 GC 時會被回收慢宗。
ThreadLocal
為什么會內(nèi)存泄漏
ThreadLocalMap
使用ThreadLocal
的弱引用作為key
,如果一個ThreadLocal
沒有外部強(qiáng)引用來引用它奔穿,那么系統(tǒng) GC 的時候镜沽,這個ThreadLocal
勢必會被回收,這樣一來贱田,ThreadLocalMap
中就會出現(xiàn)key
為null
的Entry
缅茉,就沒有辦法訪問這些key
為null
的Entry
的value
,如果當(dāng)前線程再遲遲不結(jié)束的話男摧,這些key
為null
的Entry
的value
就會一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永遠(yuǎn)無法回收蔬墩,造成內(nèi)存泄漏译打。
其實(shí),ThreadLocalMap
的設(shè)計(jì)中已經(jīng)考慮到這種情況拇颅,也加上了一些防護(hù)措施:在ThreadLocal
的get()
,set()
,remove()
的時候都會清除線程ThreadLocalMap
里所有key
為null
的value
奏司。
但是這些被動的預(yù)防措施并不能保證不會內(nèi)存泄漏:
- 使用
static
的ThreadLocal
,延長了ThreadLocal
的生命周期樟插,可能導(dǎo)致的內(nèi)存泄漏(參考ThreadLocal 內(nèi)存泄露的實(shí)例分析)韵洋。 - 分配使用了
ThreadLocal
又不再調(diào)用get()
,set()
,remove()
方法,那么就會導(dǎo)致內(nèi)存泄漏黄锤。
為什么使用弱引用
從表面上看內(nèi)存泄漏的根源在于使用了弱引用搪缨。網(wǎng)上的文章大多著重分析ThreadLocal
使用了弱引用會導(dǎo)致內(nèi)存泄漏,但是另一個問題也同樣值得思考:為什么使用弱引用而不是強(qiáng)引用鸵熟?
我們先來看看官方文檔的說法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
為了應(yīng)對非常大和長時間的用途副编,哈希表使用弱引用的 key。
下面我們分兩種情況討論:
-
key 使用強(qiáng)引用:引用的
ThreadLocal
的對象被回收了流强,但是ThreadLocalMap
還持有ThreadLocal
的強(qiáng)引用痹届,如果沒有手動刪除,ThreadLocal
不會被回收煮盼,導(dǎo)致Entry
內(nèi)存泄漏短纵。 -
key 使用弱引用:引用的
ThreadLocal
的對象被回收了,由于ThreadLocalMap
持有ThreadLocal
的弱引用僵控,即使沒有手動刪除香到,ThreadLocal
也會被回收。value
在下一次ThreadLocalMap
調(diào)用set
,get
报破,remove
的時候會被清除悠就。
比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap
的生命周期跟Thread
一樣長充易,如果都沒有手動刪除對應(yīng)key
梗脾,都會導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal
不會內(nèi)存泄漏盹靴,對應(yīng)的value
在下一次ThreadLocalMap
調(diào)用set
,get
,remove
的時候會被清除炸茧。
因此,ThreadLocal
內(nèi)存泄漏的根源是:由于ThreadLocalMap
的生命周期跟Thread
一樣長稿静,如果沒有手動刪除對應(yīng)key
就會導(dǎo)致內(nèi)存泄漏梭冠,而不是因?yàn)槿跻谩?/p>
ThreadLocal 最佳實(shí)踐
綜合上面的分析,我們可以理解ThreadLocal
內(nèi)存泄漏的前因后果改备,那么怎么避免內(nèi)存泄漏呢控漠?
- 每次使用完
ThreadLocal
,都調(diào)用它的remove()
方法,清除數(shù)據(jù)盐捷。
在使用線程池的情況下偶翅,沒有及時清理ThreadLocal
,不僅是內(nèi)存泄漏的問題碉渡,更嚴(yán)重的是可能導(dǎo)致業(yè)務(wù)邏輯出現(xiàn)問題聚谁。所以,使用ThreadLocal
就跟加鎖完要解鎖一樣爆价,用完就清理垦巴。
參考文章
Java并發(fā)包學(xué)習(xí)七 解密ThreadLocal
ThreadLocal可能引起的內(nèi)存泄露