您好,登錄后才能下訂單哦!
這篇文章主要介紹“為什么每次用完ThreadLocal都要調用remove()”,在日常操作中,相信很多人在為什么每次用完ThreadLocal都要調用remove()問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”為什么每次用完ThreadLocal都要調用remove()”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
什么是內存泄漏
內存泄漏指的是,當某一個對象不再有用的時候,占用的內存卻不能被回收,這就叫作內存泄漏。
因為通常情況下,如果一個對象不再有用,那么我們的垃圾回收器 GC,就應該把這部分內存給清理掉。這樣的話,就可以讓這部分內存后續重新分配到其他的地方去使用;否則,如果對象沒有用,但一直不能被回收,這樣的垃圾對象如果積累的越來越多,則會導致我們可用的內存越來越少,最后發生內存不夠用的 OOM 錯誤。
下面我們來分析一下,在 ThreadLocal 中這樣的內存泄漏是如何發生的。
Key 的泄漏
在上一講中,我們分析了 ThreadLocal 的內部結構,知道了每一個 Thread 都有一個 ThreadLocal.ThreadLocalMap 這樣的類型變量,該變量的名字叫作 threadLocals。線程在訪問了 ThreadLocal 之后,都會在它的 ThreadLocalMap 里面的 Entry 中去維護該 ThreadLocal 變量與具體實例的映射。
我們可能會在業務代碼中執行了 ThreadLocal instance = null 操作,想清理掉這個 ThreadLocal 實例,但是假設我們在 ThreadLocalMap 的 Entry 中強引用了 ThreadLocal 實例,那么,雖然在業務代碼中把 ThreadLocal 實例置為了 null,但是在 Thread 類中依然有這個引用鏈的存在。
GC 在垃圾回收的時候會進行可達性分析,它會發現這個 ThreadLocal 對象依然是可達的,所以對于這個 ThreadLocal 對象不會進行垃圾回收,這樣的話就造成了內存泄漏的情況。
JDK 開發者考慮到了這一點,所以 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; } }
可以看到,這個 Entry 是 extends WeakReference。弱引用的特點是,如果這個對象只被弱引用關聯,而沒有任何強引用關聯,那么這個對象就可以被回收,所以弱引用不會阻止 GC。因此,這個弱引用的機制就避免了 ThreadLocal 的內存泄露問題。
這就是為什么 Entry 的 key 要使用弱引用的原因。
Value 的泄漏
可是,如果我們繼續研究的話會發現,雖然 ThreadLocalMap 的每個 Entry 都是一個對 key 的弱引用,但是這個 Entry 包含了一個對 value 的強引用,還是剛才那段代碼:
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 這行代碼就代表了強引用的發生。
正常情況下,當線程終止,key 所對應的 value 是可以被正常垃圾回收的,因為沒有任何強引用存在了。但是有時線程的生命周期是很長的,如果線程遲遲不會終止,那么可能 ThreadLocal 以及它所對應的 value 早就不再有用了。在這種情況下,我們應該保證它們都能夠被正常的回收。
為了更好地分析這個問題,我們用下面這張圖來看一下具體的引用鏈路(實線代表強引用,虛線代表弱引用):
可以看到,左側是引用棧,棧里面有一個 ThreadLocal 的引用和一個線程的引用,右側是我們的堆,在堆中是對象的實例。
我們重點看一下下面這條鏈路:Thread Ref → Current Thread → ThreadLocalMap → Entry → Value → 可能泄漏的value實例。
這條鏈路是隨著線程的存在而一直存在的,如果線程執行耗時任務而不停止,那么當垃圾回收進行可達性分析的時候,這個 Value 就是可達的,所以不會被回收。但是與此同時可能我們已經完成了業務邏輯處理,不再需要這個 Value 了,此時也就發生了內存泄漏問題。
JDK 同樣也考慮到了這個問題,在執行 ThreadLocal 的 set、remove、rehash 等方法時,它都會掃描 key 為 null 的 Entry,如果發現某個 Entry 的 key 為 null,則代表它所對應的 value 也沒有作用了,所以它就會把對應的 value 置為 null,這樣,value 對象就可以被正常回收了。
但是假設 ThreadLocal 已經不被使用了,那么實際上 set、remove、rehash 方法也不會被調用,與此同時,如果這個線程又一直存活、不終止的話,那么剛才的那個調用鏈就一直存在,也就導致了 value 的內存泄漏。
如何避免內存泄露
分析完這個問題之后,該如何解決呢?解決方法就是我們本課時的標題:調用 ThreadLocal 的 remove 方法。調用這個方法就可以刪除對應的 value 對象,可以避免內存泄漏。
我們來看一下 remove 方法的源碼:
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
可以看出,它是先獲取到 ThreadLocalMap 這個引用的,并且調用了它的 remove 方法。這里的 remove 方法可以把 key 所對應的 value 給清理掉,這樣一來,value 就可以被 GC 回收了。
所以,在使用完了 ThreadLocal 之后,我們應該手動去調用它的 remove 方法,目的是防止內存泄漏的發生。
到此,關于“為什么每次用完ThreadLocal都要調用remove()”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。