天天看點

深入分析 ThreadLocal 記憶體洩漏問題

轉自: http://blog.csdn.net/wudiyong22/article/details/52141608

前言

ThreadLocal

 的作用是提供線程内的局部變量,這種變量線上程的生命周期内起作用,減少同一個線程内多個函數或者元件之間一些公共變量的傳遞的複雜度。但是如果濫用 

ThreadLocal

,就可能會導緻記憶體洩漏。下面,我們将圍繞三個方面來分析 

ThreadLocal

 記憶體洩漏的問題

  • ThreadLocal

     實作原理
  • ThreadLocal

    為什麼會記憶體洩漏
  • ThreadLocal

     最佳實踐

http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/#ThreadLocal__u5B9E_u73B0_u539F_u7406

ThreadLocal

實作原理

ThreadLocal

的實作是這樣的:每個

Thread

 維護一個 

ThreadLocalMap

 映射表,這個映射表的 

key

 是 

ThreadLocal

 執行個體本身,

value

 是真正需要存儲的

Object

也就是說 

ThreadLocal

 本身并不存儲值,它隻是作為一個 

key

 來讓線程從 

ThreadLocalMap

 擷取 

value

。值得注意的是圖中的虛線,表示

ThreadLocalMap

 是使用 

ThreadLocal

 的弱引用作為 

Key

 的,弱引用的對象在

GC 時會被回收。

http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/#ThreadLocal_u4E3A_u4EC0_u4E48_u4F1A_u5185_u5B58_u6CC4_u6F0F

ThreadLocal

ThreadLocalMap

使用

ThreadLocal

的弱引用作為

key

,如果一個

ThreadLocal

沒有外部強引用來引用它,那麼系統

GC 的時候,這個

ThreadLocal

勢必會被回收,這樣一來,

ThreadLocalMap

中就會出現

key

null

Entry

,就沒有辦法通路這些

key

null

Entry

value

,如果目前線程再遲遲不結束的話,這些

key

null

Entry

value

就會一直存在一條強引用鍊:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永遠無法回收,造成記憶體洩漏。

其實,

ThreadLocalMap

的設計中已經考慮到這種情況,也加上了一些防護措施:在

ThreadLocal

get()

,

set()

remove()

的時候都會清除線程

ThreadLocalMap

裡所有

key

null

value

但是這些被動的預防措施并不能保證不會記憶體洩漏:

  • 使用線程池的時候,這個線程執行任務結束,

    ThreadLocal

    對象被回收了,線程放回線程池中不銷毀,這個線程一直不被使用,導緻記憶體洩漏。
  • 配置設定使用了

    ThreadLocal

    又不再調用

    get()

    set()

    remove()

    方法,那麼這個期間就會發生記憶體洩漏。

http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/#u4E3A_u4EC0_u4E48_u4F7F_u7528_u5F31_u5F15_u7528 為什麼使用弱引用

從表面上看記憶體洩漏的根源在于使用了弱引用。網上的文章大多着重分析為什麼會記憶體洩漏,但是另一個問題也同樣值得思考:為什麼使用弱引用?為什麼不用強引用?

我們先來看看官方文檔的說法:

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

為了應對非常大和長時間的用途,哈希表使用弱引用的 key。

下面我們分兩種情況讨論:

  • key 使用強引用:引用的

    ThreadLocal

    的對象被回收了,但是

    ThreadLocalMap

    還持有

    ThreadLocal

    的強引用,如果沒有手動删除,

    ThreadLocal

    不會被回收,導緻

    Entry

    記憶體洩漏。
  • key 使用弱引用:引用的

    ThreadLocal

    的對象被回收了,由于

    ThreadLocalMap

    持有

    ThreadLocal

    的弱引用,即使沒有手動删除,

    ThreadLocal

    也會被回收。

    value

    在下一次

    ThreadLocalMap

    調用

    set

    get

    的時候會被清除。

比較兩種情況,我們可以發現:由于

ThreadLocalMap

的生命周期跟

Thread

一樣長,如果都沒有手動删除對應

key

,都會導緻記憶體洩漏,但是使用弱引用可以多一層保障:弱引用

ThreadLocal

不會記憶體洩漏,對應的

value

ThreadLocalMap

set

get

remove

是以,

ThreadLocal

記憶體洩漏的根源是:由于

ThreadLocalMap

Thread

一樣長,如果沒有手動删除對應

key

就會導緻記憶體洩漏,而不是因為弱引用。

http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/#ThreadLocal__u6700_u4F73_u5B9E_u8DF5 最佳實踐

綜合上面的分析,我們可以了解

ThreadLocal

記憶體洩漏的前因後果,那麼怎麼避免記憶體洩漏呢?

  • 每次使用完

    ThreadLocal

    ,都調用它的

    remove()

    方法,清除資料。

在使用線程池的情況下,沒有及時清理

ThreadLocal

,不僅是記憶體洩漏的問題,更嚴重的是可能導緻業務邏輯出現問題。是以,使用

ThreadLocal

就跟加鎖完要解鎖一樣,用完就清理。