溫馨提示×

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

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

為什么每次用完ThreadLocal都要調(diào)用remove()

發(fā)布時(shí)間:2021-10-18 11:52:09 來(lái)源:億速云 閱讀:124 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要介紹“為什么每次用完ThreadLocal都要調(diào)用remove()”,在日常操作中,相信很多人在為什么每次用完ThreadLocal都要調(diào)用remove()問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”為什么每次用完ThreadLocal都要調(diào)用remove()”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

什么是內(nèi)存泄漏

內(nèi)存泄漏指的是,當(dāng)某一個(gè)對(duì)象不再有用的時(shí)候,占用的內(nèi)存卻不能被回收,這就叫作內(nèi)存泄漏。

因?yàn)橥ǔG闆r下,如果一個(gè)對(duì)象不再有用,那么我們的垃圾回收器  GC,就應(yīng)該把這部分內(nèi)存給清理掉。這樣的話(huà),就可以讓這部分內(nèi)存后續(xù)重新分配到其他的地方去使用;否則,如果對(duì)象沒(méi)有用,但一直不能被回收,這樣的垃圾對(duì)象如果積累的越來(lái)越多,則會(huì)導(dǎo)致我們可用的內(nèi)存越來(lái)越少,最后發(fā)生內(nèi)存不夠用的  OOM 錯(cuò)誤。

下面我們來(lái)分析一下,在 ThreadLocal 中這樣的內(nèi)存泄漏是如何發(fā)生的。

Key 的泄漏

在上一講中,我們分析了 ThreadLocal 的內(nèi)部結(jié)構(gòu),知道了每一個(gè) Thread 都有一個(gè)  ThreadLocal.ThreadLocalMap 這樣的類(lèi)型變量,該變量的名字叫作 threadLocals。線(xiàn)程在訪(fǎng)問(wèn)了 ThreadLocal  之后,都會(huì)在它的 ThreadLocalMap 里面的 Entry 中去維護(hù)該 ThreadLocal 變量與具體實(shí)例的映射。

我們可能會(huì)在業(yè)務(wù)代碼中執(zhí)行了 ThreadLocal instance = null 操作,想清理掉這個(gè) ThreadLocal 實(shí)例,但是假設(shè)我們?cè)? ThreadLocalMap 的 Entry 中強(qiáng)引用了 ThreadLocal 實(shí)例,那么,雖然在業(yè)務(wù)代碼中把 ThreadLocal 實(shí)例置為了  null,但是在 Thread 類(lèi)中依然有這個(gè)引用鏈的存在。

GC 在垃圾回收的時(shí)候會(huì)進(jìn)行可達(dá)性分析,它會(huì)發(fā)現(xiàn)這個(gè) ThreadLocal 對(duì)象依然是可達(dá)的,所以對(duì)于這個(gè) ThreadLocal  對(duì)象不會(huì)進(jìn)行垃圾回收,這樣的話(huà)就造成了內(nèi)存泄漏的情況。

JDK 開(kāi)發(fā)者考慮到了這一點(diǎn),所以 ThreadLocalMap 中的 Entry 繼承了 WeakReference 弱引用,代碼如下所示:

static class Entry extends WeakReference<ThreadLocal<?>> {     /** The value associated with this ThreadLocal. */     Object value;      Entry(ThreadLocal<?> k, Object v) {         super(k);         value = v;     } }

可以看到,這個(gè) Entry 是 extends  WeakReference。弱引用的特點(diǎn)是,如果這個(gè)對(duì)象只被弱引用關(guān)聯(lián),而沒(méi)有任何強(qiáng)引用關(guān)聯(lián),那么這個(gè)對(duì)象就可以被回收,所以弱引用不會(huì)阻止  GC。因此,這個(gè)弱引用的機(jī)制就避免了 ThreadLocal 的內(nèi)存泄露問(wèn)題。

這就是為什么 Entry 的 key 要使用弱引用的原因。

Value 的泄漏

可是,如果我們繼續(xù)研究的話(huà)會(huì)發(fā)現(xiàn),雖然 ThreadLocalMap 的每個(gè) Entry 都是一個(gè)對(duì) key 的弱引用,但是這個(gè)  Entry 包含了一個(gè)對(duì) value 的強(qiáng)引用,還是剛才那段代碼:

static class Entry extends WeakReference<ThreadLocal<?>> {     /** The value associated with this ThreadLocal. */     Object value;       Entry(ThreadLocal<?> k, Object v) {         super(k);         value = v;     } }

可以看到,value = v 這行代碼就代表了強(qiáng)引用的發(fā)生。

正常情況下,當(dāng)線(xiàn)程終止,key 所對(duì)應(yīng)的 value  是可以被正常垃圾回收的,因?yàn)闆](méi)有任何強(qiáng)引用存在了。但是有時(shí)線(xiàn)程的生命周期是很長(zhǎng)的,如果線(xiàn)程遲遲不會(huì)終止,那么可能 ThreadLocal 以及它所對(duì)應(yīng)的  value 早就不再有用了。在這種情況下,我們應(yīng)該保證它們都能夠被正常的回收。

為了更好地分析這個(gè)問(wèn)題,我們用下面這張圖來(lái)看一下具體的引用鏈路(實(shí)線(xiàn)代表強(qiáng)引用,虛線(xiàn)代表弱引用):

為什么每次用完ThreadLocal都要調(diào)用remove()

可以看到,左側(cè)是引用棧,棧里面有一個(gè) ThreadLocal 的引用和一個(gè)線(xiàn)程的引用,右側(cè)是我們的堆,在堆中是對(duì)象的實(shí)例。

我們重點(diǎn)看一下下面這條鏈路:Thread Ref &rarr; Current Thread &rarr; ThreadLocalMap &rarr; Entry &rarr; Value &rarr;  可能泄漏的value實(shí)例。

這條鏈路是隨著線(xiàn)程的存在而一直存在的,如果線(xiàn)程執(zhí)行耗時(shí)任務(wù)而不停止,那么當(dāng)垃圾回收進(jìn)行可達(dá)性分析的時(shí)候,這個(gè) Value  就是可達(dá)的,所以不會(huì)被回收。但是與此同時(shí)可能我們已經(jīng)完成了業(yè)務(wù)邏輯處理,不再需要這個(gè) Value 了,此時(shí)也就發(fā)生了內(nèi)存泄漏問(wèn)題。

JDK 同樣也考慮到了這個(gè)問(wèn)題,在執(zhí)行 ThreadLocal 的 set、remove、rehash 等方法時(shí),它都會(huì)掃描 key 為 null 的  Entry,如果發(fā)現(xiàn)某個(gè) Entry 的 key 為 null,則代表它所對(duì)應(yīng)的 value 也沒(méi)有作用了,所以它就會(huì)把對(duì)應(yīng)的 value 置為  null,這樣,value 對(duì)象就可以被正常回收了。

但是假設(shè) ThreadLocal 已經(jīng)不被使用了,那么實(shí)際上 set、remove、rehash  方法也不會(huì)被調(diào)用,與此同時(shí),如果這個(gè)線(xiàn)程又一直存活、不終止的話(huà),那么剛才的那個(gè)調(diào)用鏈就一直存在,也就導(dǎo)致了 value 的內(nèi)存泄漏。

如何避免內(nèi)存泄露

分析完這個(gè)問(wèn)題之后,該如何解決呢?解決方法就是我們本課時(shí)的標(biāo)題:調(diào)用 ThreadLocal 的 remove  方法。調(diào)用這個(gè)方法就可以刪除對(duì)應(yīng)的 value 對(duì)象,可以避免內(nèi)存泄漏。

我們來(lái)看一下 remove 方法的源碼:

public void remove() {     ThreadLocalMap m = getMap(Thread.currentThread());     if (m != null)         m.remove(this); }

可以看出,它是先獲取到 ThreadLocalMap 這個(gè)引用的,并且調(diào)用了它的 remove 方法。這里的 remove 方法可以把 key 所對(duì)應(yīng)的  value 給清理掉,這樣一來(lái),value 就可以被 GC 回收了。

所以,在使用完了 ThreadLocal 之后,我們應(yīng)該手動(dòng)去調(diào)用它的 remove 方法,目的是防止內(nèi)存泄漏的發(fā)生。

到此,關(guān)于“為什么每次用完ThreadLocal都要調(diào)用remove()”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

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

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

AI