溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么

發(fā)布時(shí)間:2021-06-22 16:57:27 來源:億速云 閱讀:734 作者:Leah 欄目:編程語言

這篇文章給大家介紹threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

內(nèi)存泄漏的現(xiàn)象 執(zhí)行 cn.enjoyedu.ch2.threadlocal 下的 ThreadLocalOOM,并將堆內(nèi)存大小設(shè) 置為-Xmx256m, 我們啟用一個(gè)線程池,大小固定為 5 個(gè)線程

threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么

場景 1:首先任務(wù)中不執(zhí)行任何有意義的代碼,當(dāng)所有的任務(wù)提交執(zhí)行完成 后,可以看見,我們這個(gè)應(yīng)用的內(nèi)存占用基本上為 25M 左右

threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么

場景 2:然后我們只簡單的在每個(gè)任務(wù)中 new 出一個(gè)數(shù)組,執(zhí)行完成后我們 可以看見,內(nèi)存占用基本和場景 1 同

threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么

場景 3:當(dāng)我們啟用了 ThreadLocal 以后:,執(zhí)行完成后我們可以看見,內(nèi)存占用變?yōu)榱?100M 左右

threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么

場景 4:于是,我們加入一行代碼,再執(zhí)行,看看內(nèi)存情況:

threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么

可以看見,內(nèi)存占用基本和場景 1 同。 這就充分說明,場景 3,當(dāng)我們啟用了 ThreadLocal 以后確實(shí)發(fā)生了內(nèi)存泄 漏。

分析 :

        根據(jù)我們前面對(duì) ThreadLocal 的分析,我們可以知道每個(gè) Thread 維護(hù)一個(gè) ThreadLocalMap,這個(gè)映射表的 key 是 ThreadLocal 實(shí)例本身,value 是真正需 要存儲(chǔ)的 Object,也就是說 ThreadLocal 本身并不存儲(chǔ)值,它只是作為一個(gè) key 來讓線程從 ThreadLocalMap 獲取 value。仔細(xì)觀察 ThreadLocalMap,這個(gè) map 是使用 ThreadLocal 的弱引用作為 Key 的,弱引用的對(duì)象在 GC 時(shí)會(huì)被回收。因此使用了 ThreadLocal 后,引用鏈如下圖:

threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么

圖中的虛線表示弱引用。

        這樣,當(dāng)把 threadlocal 變量置為 null 以后,沒有任何強(qiáng)引用指向 threadlocal 實(shí)例,所以 threadlocal 將會(huì)被 gc 回收。導(dǎo)致ThreadLocalMap 中就會(huì)出現(xiàn) key 為 null 的 Entry,就沒有辦法訪問這些 key 為 null 的 Entry 的 value,如果當(dāng)前 線程再遲遲不結(jié)束的話,這些 key 為 null 的 Entry 的 value 就會(huì)一直存在一條強(qiáng) 引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而這塊 value 永 遠(yuǎn)不會(huì)被訪問到了,所以存在著內(nèi)存泄露。

        只有當(dāng)前 thread 結(jié)束以后,current thread 就不會(huì)存在棧中,強(qiáng)引用斷開, Current Thread、Map value 將全部被 GC 回收。最好的做法是不在需要使用 ThreadLocal 變量后,都調(diào)用它的 remove()方法,清除數(shù)據(jù)。

        所以回到我們前面的實(shí)驗(yàn)場景,場景 3 中,雖然線程池里面的任務(wù)執(zhí)行完畢 了,但是線程池里面的 5 個(gè)線程會(huì)一直存在直到 JVM 退出,我們 set 了線程的 localVariable 變量后沒有調(diào)用 localVariable.remove()方法,導(dǎo)致線程池里面的 5 個(gè) 線程的 threadLocals 變量里面的 new LocalVariable()實(shí)例沒有被釋放。

        其實(shí)考察 ThreadLocal 的實(shí)現(xiàn),我們可以看見,無論是 get()、set()在某些時(shí) 候,調(diào)用了 expungeStaleEntry 方法用來清除 Entry 中 Key 為 null 的 Value,但是 這是不及時(shí)的,也不是每次都會(huì)執(zhí)行的,所以一些情況下還是會(huì)發(fā)生內(nèi)存泄露。 只有 remove()方法中顯式調(diào)用了 expungeStaleEntry 方法。

        從表面上看內(nèi)存泄漏的根源在于使用了弱引用,但是另一個(gè)問題也同樣值得 思考:為什么使用弱引用而不是強(qiáng)引用?

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

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

        key 使用弱引用:對(duì) ThreadLocal 對(duì)象實(shí)例的引用被被置為 null 了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使沒有手動(dòng)刪除,ThreadLocal 的 對(duì)象實(shí)例也會(huì)被回收。value 在下一次 ThreadLocalMap 調(diào)用 set,get,remove 都 有機(jī)會(huì)被回收。

        比較兩種情況

                我們可以發(fā)現(xiàn):由于 ThreadLocalMap 的生命周期跟 Thread 一樣長,如果都沒有手動(dòng)刪除對(duì)應(yīng) key,都會(huì)導(dǎo)致內(nèi)存泄漏,但是使用弱引用可 以多一層保障。

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

        總結(jié):

                JVM 利用設(shè)置 ThreadLocalMap 的 Key 為弱引用,來避免內(nèi)存泄露。

                JVM 利用調(diào)用 remove、get、set 方法的時(shí)候,回收弱引用。

                當(dāng) ThreadLocal 存儲(chǔ)很多 Key 為 null 的 Entry 的時(shí)候,而不再去調(diào)用 remove、 get、set 方法,那么將導(dǎo)致內(nèi)存泄漏。

                使用線程池+ ThreadLocal 時(shí)要小心,因?yàn)檫@種情況下,線程是一直在不斷的 重復(fù)運(yùn)行的,從而也就造成了 value 可能造成累積的情況。

關(guān)于threadLocal出現(xiàn)內(nèi)存泄漏的原因是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI