1. 程式人生 > 程式設計 >ThreadLocal記憶體洩漏問題解決方案

ThreadLocal記憶體洩漏問題解決方案

如果說 ThreadLocal 的話,那肯定就會涉及到記憶體洩漏,為啥嘞

因為 吧啦吧啦 ~

ThreadLocal 解決了什麼問題呢?

它是為了解決物件不能被多執行緒共享訪問的問題,通過 threadLocal.set() 方法將物件例項儲存在每個執行緒自己所擁有的 threadLocalMap 中,這樣的話每個執行緒都使用自己的物件例項,彼此不會影響從而達到了隔離的作用,這樣就解決了物件在被共享訪問時帶來的執行緒安全問題。

啥意思呢?打個比方,現在公司所有人都要填寫一個表格,但是隻有一支筆,這個時候就只能上個人用完了之後,下個人才可以使用,為了保證"筆"這個資源的可用性,只需要保證在接下來每個人的獲取順序就可以了,這就是 lock 的作用,當這支筆被別人用的時候,我就加 lock ,你來了那就進入佇列排隊等待獲取資源(非公平方式那就另外說了),這支筆用完之後就釋放 lock ,然後按照順序給下個人使用。

但是完全可以一個人一支筆對不對,這樣的話,你填寫你的表格,我填寫我的表格,咱倆誰都不耽擱誰。這就是 ThreadLocal 在做的事情,因為每個 Thread 都有一個副本,就不存在資源競爭,所以也就不需要加鎖,這不就是拿空間去換了時間嘛!

在開始之前,咱們先把 Thread, ThreadLocal, ThreadLocalMap 的關係捋一捋:

ThreadLocal記憶體洩漏問題解決方案

可以看到,在 Thread 中持有一個 ThreadLocalMap , ThreadLocalMap 又是由 Entry 來組成的,在 Entry 裡面有 ThreadLocal 和 value

ThreadLocal 為啥動不動就記憶體洩漏呢?

在這裡先給個解釋,後面咱們再詳細分析:

首先是因為 ThreadLocal 是基於 ThreadLocalMap 實現的,其中 ThreadLocalMap 的 Entry 繼承了 WeakReference ,而 Entry 物件中的 key 使用了 WeakReference 封裝,也就是說, Entry 中的 key 是一個弱引用型別,對於弱引用來說,它只能存活到下次 GC 之前

如果此時一個執行緒呼叫了 ThreadLocalMap 的 set 設定變數,當前的 ThreadLocalMap 就會新增一條記錄,但由於發生了一次垃圾回收,這樣就會造成一個結果: key 值被回收掉了,但是 value 值還在記憶體中,而且如果執行緒一直存在的話,那麼它的 value 值就會一直存在

這樣被垃圾回收掉的 key 就會一直存在一條引用鏈: Thread -> ThreadLocalMap -> Entry -> Value :

ThreadLocal記憶體洩漏問題解決方案

就是因為這條引用鏈的存在,就會導致如果 Thread 還在執行,那麼 Entry 不會被回收,進而 value 也不會被回收掉,但是 Entry 裡面的 key 值已經被回收掉了

這只是一個執行緒,如果再來一個執行緒,又來一個執行緒…多了之後就會造成記憶體洩漏

知道是怎麼造成記憶體洩漏之後,接下來要做的事情就好說了,不是因為 value 值沒有被回收掉所以才會導致記憶體洩露的嘛

那使用完 key 值之後,將 value 值通過 remove 方法 remove 掉,這樣的話記憶體中就不會有 value 值了,也就防止了記憶體洩漏嘛

ThreadLocal 是基於 ThreadLocalMap 實現的?

OK ,上面的內容講完了,接下來一一來看

首先,你怎麼知道 ThreadLocal 是基於 ThreadLocalMap 實現的呢?

從原始碼知道的~

在原始碼中能夠看到下面這幾行程式碼:

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

程式碼中說的很清楚了,在 ThreadLocal 內部維護著 ThreadLocalMap ,而它的 Entry 則繼承自 WeakReference 的 ThreadLocal,其中 Entry 的 k 為 ThreadLocal , v 為 Object ,在呼叫 super(k) 時就將 ThreadLocal 例項包裝成了一個 WeakReference

強弱引用這塊內容阿粉就直接放一個表格吧:

引用型別 功能特點
強引用 ( Strong Reference ) 被強引用關聯的物件永遠不會被垃圾回收器回收掉
軟引用( Soft Reference ) 軟引用關聯的物件,只有當系統將要發生記憶體溢位時,才會去回收軟引用引用的物件
弱引用 ( Weak Reference ) 只被弱引用關聯的物件,只要發生垃圾收集事件,就會被回收
虛引用 ( Phantom Reference ) 被虛引用關聯的物件的唯一作用是能在這個物件被回收器回收時收到一個系統通知

從表格中應該能夠看出來,弱引用的物件只要發生垃圾收集事件,就會被回收

所以弱引用的存活時間也就是下次 GC 之前了

在這裡阿粉就有個問題想問問了:為什麼 ThreadLocal 採用弱引用,而不是強引用嘞?

在 ThreadLocalMap 上面有些註釋,我在這裡摘錄一部分,或許可以從中窺探一二:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys

翻譯一下就是:(雖然我英語不是很好

為了解決非常大且長期使用的問題,雜湊表使用了弱引用的 key

假設,假設, ThreadLocal 使用的是強引用,會怎樣呢?

如果是強引用的話,在表格中也能夠看出來,被強引用關聯的物件,永遠都不會被垃圾回收器回收掉

如果引用的 ThreadLocal 物件被回收了,但是 ThreadLocalMap 還持有對 ThreadLocal 的強引用,如果沒有 remove 的話, 在 GC 時進行可達性分析, ThreadLocal 依然可達,這樣就不會對 ThreadLocal 進行回收,但是我們期望的是引用的 ThreadLocal 物件被回收,這樣不就達不到目的了嘛

使用弱引用的話,雖然會出現記憶體洩漏的問題,但是在 ThreadLocal 生命週期裡面,都有對 key 值為 null 時進行回收的處理操作

所以,使用弱引用的話,可以在 ThreadLocal 生命週期中儘可能保證不出現記憶體洩漏的問題

啥?在 ThreadLcoal 生命週期裡面,都有對 key 值為 null 時進行回收的處理操作?有證據麼?

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。