問題一:為什么 ThreadLocalMap 的 key 是弱引用?
【假設 Entry 的 key 是對 ThreadLocal 對象的強引用】:這個 Entry
又持有 ThreadLocal 對象和 value 對象的強引用。如果在其他地方都沒有對這個 ThreadLocla 對象的引用了、然后在使用 ThreadLocalMap 的過程中又沒有正確地在用完后就調用 remove 方法、所以這個 ThreadLocal 對象和所關聯的 value 對象就會跟隨著線程一直存在、這樣就會可能會造成內存泄漏問題。
特別是在使用線程池的時候、核心線程是會一直存在直到程序結束、如果這些線程中的 ThreadLocalMap 中的數據沒有被及時清理、就會一直占用內存、而且在線程復用時可能會導致數據錯亂的危險。
【Entry 的 key 是對 ThreadLocal 對象的弱引用】:弱引用就意味著、如果沒有其他引用對象的強引用關系、那么這個僅被弱引用引用著的對象在下次 GC 時就會被回收掉、這樣在一定程度上降低內存泄漏的風險。但同時也引入了新的問題、key 雖然被回收了、但是 value 對象還在、我們無法獲取、也無法刪除、這樣也會存在內存泄漏的風險。
雖然 ThreadLocalMap 中在進行 set 和 get 操作時會進行啟發式清理和探測式清理、清理一部分 key 為 null 的 Entry 對象、但是這也只是一種后備選擇方案
最重要的還是開發人員在編寫代碼時記得在使用完數據后及時調用 remove() 方法手動清理
補充:
【內存泄漏就是:有些對象已經不再使用了、但是由于沒有正確處理對象的引用關系、使得這個無用的對象還一直被 GC Root 直接或間接引用著、垃圾回收時就無法清理掉這些對象、如果這類對象存在很多、就會導致內存泄漏。簡單地說就是有些無用對象占用著寶貴的內存空間、但又沒辦法清理掉它們 可達性分析是現代垃圾回收器用來判斷對象是否存活的核心算法】
問題二:為什么 ThreadLocalMap 的 value 是強引用?
【假設Entry 的 value 是弱引用】:假設 key 所引用的 ThreadLocal 對象還被其他的引用對象強引用著,那么這個 ThreadLocal 對象就不會被 GC 回收、但如果 value 是弱引用且不被其他引用對象引用著、那 GC 的時候就被回收掉了、那線程通過 ThreadLocal 來獲取 value 的時候就會獲得 null,顯然這不是我們希望的結果。因為對我們來說、value 才是我們想要保存的數據、ThreadLcoal 只是用來關聯 value 的、如果 value 都沒了、還要 ThreadLocal 干啥呢
面試參考回答:
面試官您好關于 ThreadLocalMap 的 key 使用弱引用、value 使用強引用的問題
我的理解是這樣的:
首先要理解這樣設計的目的是為了盡可能地避免內存泄漏。
-
Key 使用弱引用: 假設 key 是強引用、那么即使 ThreadLocal 對象本身已經沒有其他地方引用了、由于 ThreadLocalMap 中 Entry 的強引用、這個 ThreadLocal 對象仍然無法被垃圾回收。如果線程一直存活(比如線程池中的線程)、這個 ThreadLocal 對象和對應的 value 就會一直占用內存、造成內存泄漏。使用弱引用、當 ThreadLocal 對象沒有外部強引用時、在下次 GC 的時候、key 就會被回收、降低了內存泄漏的風險。
-
Value 使用強引用: Value 是我們真正想要存儲的數據,如果 value 也使用弱引用、那么在 ThreadLocal 對象還存活的情況下、
value
卻可能因為沒有強引用而被 GC 回收、導致我們通過 ThreadLocal 獲取到的 value 為空、這顯然是不符合ThreadLocal
的設計目的的。ThreadLocal 的作用就是關聯數據、如果數據都沒了ThreadLocal 就失去了意義。
而且雖然 key 使用弱引用可以降低內存泄漏的風險、但仍然存在 value 無法回收的問題。
當 key 被回收后value 仍然被 Entry 強引用。如果線程一直存活、這個 value 就會一直占用內存。
因此ThreadLocalMap 在 set()
和 get()
操作時會進行啟發式清理、移除 key 為 null 的 Entry 但這只是一個補救措施。
最根本的解決辦法還是需要開發者在使用完 ThreadLocal 后、手動調用 remove()
方法、及時清理 ThreadLocalMap 中的 Entry,避免內存泄漏。
總結: 弱引用 key 降低了 ThreadLocal 對象本身的內存泄漏風險、強引用 value 保證了數據的可用性。
但最終避免內存泄漏、需要開發者養成良好的習慣、及時清理 ThreadLocal。