深入分析 ThreadLocal 內(nèi)存泄漏問(wèn)題

前言

ThreadLocal?的作用是提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或者組件之間一些公共變量的傳遞的復(fù)雜度。但是如果濫用?ThreadLocal,就可能會(huì)導(dǎo)致內(nèi)存泄漏。下面,我們將圍繞三個(gè)方面來(lái)分析?ThreadLocal?內(nèi)存泄漏的問(wèn)題

ThreadLocal?實(shí)現(xiàn)原理

ThreadLocal為什么會(huì)內(nèi)存泄漏

ThreadLocal?最佳實(shí)踐

ThreadLocal 實(shí)現(xiàn)原理

ThreadLocal的實(shí)現(xiàn)是這樣的:每個(gè)Thread?維護(hù)一個(gè)?ThreadLocalMap?映射表,這個(gè)映射表的?key?是?ThreadLocal?實(shí)例本身,value?是真正需要存儲(chǔ)的?Object。

也就是說(shuō)?ThreadLocal?本身并不存儲(chǔ)值,它只是作為一個(gè)?key?來(lái)讓線程從?ThreadLocalMap?獲取?value。值得注意的是圖中的虛線,表示?ThreadLocalMap?是使用?ThreadLocal?的弱引用作為?Key?的,弱引用的對(duì)象在 GC 時(shí)會(huì)被回收。

ThreadLocal為什么會(huì)內(nèi)存泄漏

ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個(gè)ThreadLocal沒(méi)有外部強(qiáng)引用來(lái)引用它,那么系統(tǒng) GC 的時(shí)候,這個(gè)ThreadLocal勢(shì)必會(huì)被回收,這樣一來(lái),ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry,就沒(méi)有辦法訪問(wèn)這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠(yuǎn)無(wú)法回收,造成內(nèi)存泄漏。

其實(shí),ThreadLocalMap的設(shè)計(jì)中已經(jīng)考慮到這種情況,也加上了一些防護(hù)措施:在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)清除線程ThreadLocalMap里所有key為null的value。

但是這些被動(dòng)的預(yù)防措施并不能保證不會(huì)內(nèi)存泄漏:

使用static的ThreadLocal,延長(zhǎng)了ThreadLocal的生命周期,可能導(dǎo)致的內(nèi)存泄漏。

為什么使用弱引用

從表面上看內(nèi)存泄漏的根源在于使用了弱引用。網(wǎng)上的文章大多著重分析ThreadLocal使用了弱引用會(huì)導(dǎo)致內(nèi)存泄漏,但是另一個(gè)問(wèn)題也同樣值得思考:為什么使用弱引用而不是強(qiáng)引用?

我們先來(lái)看看官方文檔的說(shuō)法:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.

為了應(yīng)對(duì)非常大和長(zhǎng)時(shí)間的用途,哈希表使用弱引用的 key。

下面我們分兩種情況討論:

key 使用強(qiáng)引用:引用的ThreadLocal的對(duì)象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用,如果沒(méi)有手動(dòng)刪除,ThreadLocal不會(huì)被回收,導(dǎo)致Entry內(nèi)存泄漏。

key 使用弱引用:引用的ThreadLocal的對(duì)象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒(méi)有手動(dòng)刪除,ThreadLocal也會(huì)被回收。value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。

比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果都沒(méi)有手動(dòng)刪除對(duì)應(yīng)key,都會(huì)導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會(huì)內(nèi)存泄漏,對(duì)應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。

因此,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果沒(méi)有手動(dòng)刪除對(duì)應(yīng)key就會(huì)導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/p>

ThreadLocal 最佳實(shí)踐

綜合上面的分析,我們可以理解ThreadLocal內(nèi)存泄漏的前因后果,那么怎么避免內(nèi)存泄漏呢?

每次使用完ThreadLocal,都調(diào)用它的remove()方法,清除數(shù)據(jù)。

在使用線程池的情況下,沒(méi)有及時(shí)清理ThreadLocal,不僅是內(nèi)存泄漏的問(wèn)題,更嚴(yán)重的是可能導(dǎo)致業(yè)務(wù)邏輯出現(xiàn)問(wèn)題。所以,使用ThreadLocal就跟加鎖完要解鎖一樣,用完就清理。

參考文章

Java并發(fā)包學(xué)習(xí)七 解密ThreadLocal

ThreadLocal可能引起的內(nèi)存泄露

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 ThreadLocal 的作用是提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)...
    Jk_zhuang閱讀 455評(píng)論 0 4
  • 原創(chuàng)文章&經(jīng)驗(yàn)總結(jié)&從校招到A廠一路陽(yáng)光一路滄桑 詳情請(qǐng)戳www.codercc.com 1. 造成內(nèi)存泄漏的原因...
    你聽(tīng)___閱讀 31,274評(píng)論 20 66
  • 前言 ThreadLocal很多同學(xué)都搞不懂是什么東西,可以用來(lái)干嘛。但面試時(shí)卻又經(jīng)常問(wèn)到,所以這次我和大家一起學(xué)...
    liangzzz閱讀 12,661評(píng)論 14 228
  • 最近愛(ài)上了整理舊物,總是能淘出驚喜。發(fā)現(xiàn)了高中的日記本,要說(shuō)來(lái)讓我覺(jué)得自己都了不起的事情就是我一直在堅(jiān)持寫(xiě)日記。記...
    a8279cbde692閱讀 690評(píng)論 0 1
  • 魯迅先生曾經(jīng)寫(xiě)部作品《彷徨》,他可能是在感嘆中國(guó)未來(lái)的命運(yùn)吧!我只是一個(gè)小小學(xué)生,沒(méi)有大家的遠(yuǎn)見(jiàn),快到學(xué)業(yè)最緊張的...
    十七塊錢(qián)閱讀 343評(píng)論 0 0

友情鏈接更多精彩內(nèi)容