1. 程式人生 > >【學習筆記】ThreadLocal與引用型別相關知識點

【學習筆記】ThreadLocal與引用型別相關知識點

## 0 寫在前邊 今天以 “TheadLocal 為什麼會導致記憶體洩漏” 為題與朋友們討論了一波,引出了一些原理性的內容,本文就這個問題作答,並擴充套件相關的知識點 ## 1 ThreadLocal 和 ThreadLocalMap 是什麼? 簡單來說,ThreadLocal 是一種操作與執行緒繫結的共享物件的工具,通過ThreadLocal可以將一些物件儲存線上程上,實現同線程不同方法之間的物件共享。 執行緒的上下文由 ThreadLocalMap 組成,它是 ThreadLocal 的靜態內部類,儲存著執行緒共享物件。 一般來說,我們無需顯式建立ThreadLocalMap,也無需為裝入ThreadLocalMap 物件設 key 值,因為在 set 方法執行時會建立 ThreadLocalMap,並將當前 ThreadLocal 物件作為 key,待儲存物件作為 value,儲存到 ThreadLocalMap。 值得一提的是,ThreadLocalMap 的 key 與 value 的型別是不同的,key 是弱引用型別的,value 是強引用型別的。 ## 2 Thread、ThreadLocal 與 ThreadLocalMap 之間的關係 ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200805135341719-535568432.png) **Thread 與 ThreadLocalMap** 首先 ThreadLocalMap 是與 Thread 進行繫結的,ThreadLocalMap 是執行緒上實際儲存共享物件的容器。 如下圖,`threadLocals` 就是預設的 ThreadLocalMap,預設為 `null` ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200805091216526-2021322069.png) 繫結 ThreadLocalMap 到 Thread 的位置在 ThreadLocal 的 `createMap` 方法中,`threadLocals` 引用指向 ThreadLocalMap。(這裡還包含了放置第一個物件的操作) ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200805131305052-3605104.png) ThreadLocal 的 `getMap` 方法取的就是執行緒的 `threadLocals` ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200805131604748-1104167511.png) **ThreadLocal與ThreadLocalMap** ThreadLocalMap 是 ThreadLocal 類的靜態內部類,ThreadLocal 是操作 ThreadLocalMap 的工具,還是 ThreadLocalMap 的 key 物件,在 ThreadLocal 作為 key 儲存前轉換成弱引用型別。 一般我們通過 ThreadLocal 的 set 方法進行儲存物件,在 `set` 方法內部獲取了當前執行緒的 ThreadLocalMap,呼叫 ThreadLocalMap 的 `set` 方法進行儲存物件。 使用 this 關健字將當前使用的 ThreadLocal 物件作為 key 存到 ThreadLocalMap 中,以減小 key 衝突的可能性。 ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200805092642881-1375101282.png) ThreadLocalMap 中的 set 方法主要是建立一個 Entry 物件放進陣列中,Entry 繼承 WeakReference 類,將 Entry 的 key(也就是 ThreadLocal)轉成弱型別。 ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200805132113946-222400395.png) **一句話總結它們之間的關係** 每個 Thread 繫結 ThreadLocalMap 來儲存執行緒上下文共享物件,ThreadLocalMap 中的key(即,ThreadLocal)在同一執行緒中是唯一的。單執行緒情況下,每個 ThreadLocal 只對應一個值物件。 ## 3 ThreadLocal導致的記憶體洩漏的原因是什麼? **導致記憶體洩漏的原因在於程式設計師未在使用完ThreadLocalMap中儲存的物件後清除這些物件。** ThreadLocalMap是維護在Thread內部的,意味著只要執行緒不退出,ThreadLocalMap中儲存的物件引用就會一直存在,由於垃圾回收器是依據可達性分析的,存在強引用的物件不會被回收,而ThreadLocalMap中儲存的物件都是強引用的。 假設當前執行緒處於一個死迴圈中(比如,Tomcat),隨著ThreadLocalMap儲存的物件越來越多,垃圾收集器無法回收強引用的物件,就會導致可用堆記憶體越來越小,出現記憶體洩漏,最終丟擲OOM。 ## 4 如何清理 ThreadLocalMap 儲存的物件? **用完 ThreadLocal 儲存的物件後,只需呼叫 ThreadLocal 的 remove 方法,就會自動將 ThreadLocalMap 中的 K-V 對引用置空,垃圾收集器會在合適的時機內清除 K-V 物件釋放記憶體。** ThreadLocal 類 remove 方法,獲取當前執行緒上的 ThreadLocalMap 移除以此 ThreadLocal 為 key 的物件。通過呼叫 ThreadLocalMap 的 remove 方法實現。 ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200804164857442-414080605.png) ThreadLocalMap 的 remove 方法中,`e.clear()` 呼叫的是key物件繼承的 Reference 類的 `clear()`,對 key 引用置空,`expungeStaleEntry(i)` 對 value 引用置空。 ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200804164702767-136417906.png) ThreadLocalMap 的 `expungeStaleEntry` 方法,分別取出 ThreadLocalMap 中的 Entry 的 value 與 Entry 本身先後置空。 ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200804165358442-299177493.png) ## 5 為什麼ThreadLocalMap使用弱引用key? ThreadLocalMap 是與執行緒繫結的,執行緒不退出,強引用的key物件就不會被垃圾回收,當用戶妥善處理的無用K-V物件就會導致記憶體洩漏。利用弱引用可以及時被 GC 的特性,回收絕大多數key(除 static 域的全域性 key 外),以減緩記憶體洩漏。 實際上最需要回收的是value物件,弱引用key只是一種挽救措施。 ## 6 ThreadLocalMap 為什麼使用強引用 value,而不是弱引用? 與 key 不同的是,key 僅作為索引,實際工作的是 value,value 需要共享。 當局部 value 物件所在的方法結束,棧楨被清空時,會將區域性 value 物件引用銷燬,垃圾收集器會清除沒有引用的物件。 如果此時設定成弱引用裝入 Map,value 物件會在某次 GC 時消亡,這肯定不是我們希望的。 我們希望的是value物件可以維持存活以共享,只有強引用可以達到目的。 ## 7 執行緒池會累積 ThreadLocalMap 的佔用的記憶體而出現記憶體洩漏嗎? 解釋下問題,之前有講過,ThreadLocalMap 與 Thread 的生命週期是一致的,而執行緒池技術是複用執行緒的,如果之前的 ThreadLocalMap 已經開始記憶體洩漏,是否會出現累積已洩漏的記憶體? 執行緒池不存在這個問題,雖然它複用了執行緒,但是清除了上一執行緒的所有資源。 ## 8 執行緒有一個ThreadLocalMap,ThreadLocal也只有一個值,為何還會記憶體洩漏? 這是我自己思考時提出來的,能問出這個問題,只能說當時還沒完全理解ThreadLocal與ThreadLocalMap的對應關係。 原問題:一個執行緒有一個ThreadLocalMap(不考慮繼承ThreadLocal的那個實現),即然 ThreadLocal 作為 key 了,那麼ThreadLocalMap中是否只會有一個Entry,記憶體再洩露能洩露到哪裡去?(誤認為ThreadLocalMap與ThreadLocal繫結,只有一個,也只能裝一個Entry,這是錯誤的) 其實 ThreadLocal 我們可以建立很多個,ThreadLocalMap卻只有一個(不考慮繼承ThreadLocal的那個實現),通過建立多個 ThreadLocal 來存取 ThreadLocalMap 中的物件。 虛擬碼舉例: ```java ThreadLocal aThreadLocal = new ThreadLocal(); ThreadLocal bThreadLocal = new ThreadLocal(); aThreadLocal.set(new A("a")); bThreadLocal.set(new B("b")); aThreadLocal.get(); bThreadLocal.get(); ``` 我在ThreadLocal的getMap()打了斷點,當前執行緒中 ThreadLocalMap 中有兩個物件,可以看到referent中記錄了儲存物件的ThreadLocal物件的HashCode。這起碼證明了ThreadLocalMap不僅僅能裝一個物件 ![](https://img2020.cnblogs.com/blog/1149398/202008/1149398-20200805163744331-1285293359.png) ## 9 【擴充套件】Java物件的引用型別 - 強引用:常見new的物件,只要還有強引用的物件,則不會被GC - 軟引用:比強引用弱,僅當JVM記憶體不足時才會清理,清理時機在OOM前 - 弱引用:只提供非強制的對映關係,會被JVM擇機清理 - 虛引用(幻象引用):無法通過它訪問物件,只確保物件在finalize後執行某些操作 > 轉載請註明出處:【Hellxz】 https://www.cnblogs.com/hellxz/p/java-threadlo