ThreadLocal真的会造成内存泄漏吗?
ThreadLoca在并發場景中,應用非常多。那ThreadLocal是不是真的會造成內存泄漏?今天給大家做一個分享,個人見解,僅供參考。
1、ThreadLocal的基本原理
簡單介紹一下ThreadLocal,在多線程并發訪問同一個共享變量的情況下,如果不做同步控制的話,就可能會導致數據不一致的問題,所以,我們需要使用synchronized加鎖來解決。
而ThreadLocal換了一個思路來處理多線程的情況,
ThreadLocal本身并不存儲數據,它使用了線程中的threadLocals屬性,threadLocals的類型就是在ThreadLocal中的定義的ThreadLocalMap對象,當調用ThreadLocal的set(T value)方法時,ThreadLocal將自身的引用也就是this作為Key,然后,把用戶傳入的值作為Value存儲到線程的ThreadLocalMap中,這就相當于每個線程的讀寫操作都是基于線程自身的一個私有副本,線程之間的數據是相互隔離的,互不影響。
這樣一來基于ThreadLocal的操作也就不存在線程安全問題了。它相當于采用了用空間來換時間的思路,從而提高程序的執行效率。
2、四種對象引用
在ThreadLocalMap內部,維護了一個Entry數組table的屬性,用來存儲鍵值對的映射關系,來看這樣一段代碼片段:
static class ThreadLocalMap {
...
private Entry[] table;
static class Entry implements WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
Entry將ThreadLocal作為Key,值作為Value保存,它繼承自WeakReference,注意構造函數里的第一行代碼super(k),這意味著ThreadLocal對象是一個「弱引用」。有的小伙伴可能對「弱引用」不太熟悉,這里再介紹一下Java的四種引用關系。
在JDK1.2之后,Java對引用的概念做了一些擴充,將引用分為“強”、“軟”、“弱”、“虛”四種,由強到弱依次為:
強引用:指代碼中普遍存在的賦值行為,如:Object o = new Object(),只要強引用關系還在,對象就永遠不會被回收。
軟引用:還有用處,但不是必須存活的對象,JVM會在內存溢出前對其進行回收,例如:緩存。
弱引用:非必須存活的對象,引用關系比軟引用還弱,不管內存是否夠用,下次GC一定回收。
虛引用:也稱“幽靈引用”、“幻影引用”,最弱的引用關系,完全不影響對象的回收,等同于沒有引用,虛引用的唯一的目的是對象被回收時會收到一個系統通知。
這個描述還是比較官方的,簡單總結一下,大家應該都追過劇,強引用就好比是男主角,怎么都死不了。軟引用就像女主角,雖有一段經歷,還是沒走到最后。弱引用就是男二號,注定用來犧牲的。虛引用就是路人甲了。
3、造成內存泄漏的原因
內存泄漏和ThreadLocalMap中定義的Entry類有非常大的關系。
3.1內存泄漏相關概念
Memory overflow:內存溢出,沒有足夠的內存提供申請者使用。
Memory leak:內存泄漏是指程序中己動態分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果。I內存泄漏的堆積終將導致內存溢出。
3.2 如果key使用強引用
假設ThreadLocalMap中的key使用了強引用,那么會出現內存泄漏嗎?
此時ThreadLocal的內存圖(實線表示強引用)如下:
- 假設在業務代碼中使用完ThreadLocal, ThreadLocal ref被回收了
- 但是因為threadLocalMap的Entry強引用了threadLocal, 造成ThreadLocal無法被回收
- 在沒有手動刪除Entry以及CurrentThread依然運行的前提下, 始終有強引用鏈threadRef → currentThread → entry, Entry就不會被回收( Entry中包括了ThreadLocal實例和value), 導致Entry內存泄漏也就是說: ThreadLocalMap中的key使用了強引用, 是無法完全避免內存泄漏的
3.3 如果key使用弱引用
假設ThreadLocalMap中的key使用了弱引用, 那么會出現內存泄漏嗎?
- 假設在業務代碼中使用完ThreadLocal, ThreadLocal ref被回收了
- 由于threadLocalMap只持有ThreadLocal的弱引用, 沒有任何強引用指向threadlocal實例, 所以threadlocal就可以順利被gc回收, 此時Entry中的key = null
- 在沒有手動刪除Entry以及CurrentThread依然運行的前提下, 也存在有強引用鏈threadRef → currentThread → value, value就不會被回收, 而這塊value永遠不會被訪問到了, 導致value內存泄漏也就是說: ThreadLocalMap中的key使用了弱引用, 也有可能內存泄漏。
3.4 內存泄漏的真實原因
出現內存泄漏的真實原因出改以上兩種情況
比較以上兩種情況,我們就會發現:
內存泄漏的發生跟 ThreadLocalIMap 中的 key 是否使用弱引用是沒有關系的。那么內存泄漏的的真正原因是什么呢?
細心的同學會發現,在以上兩種內存泄漏的情況中.都有兩個前提:
- 沒有手動側除這個 Entry
- CurrentThread 依然運行
第一點很好理解,只要在使用完下 ThreadLocal ,調用其 remove 方法翻除對應的 Entry ,就能避免內存泄漏。
第二點稍微復雜一點,由于ThreodLocalMap 是 Threod 的一個屬性,被當前線程所引甲丁所以它的生命周期跟 Thread 一樣長。那么在使用完 ThreadLocal 的使用,如果當前Thread 也隨之執行結束, ThreadLocalMap 自然也會被 gc 回收,從根源上避免了內存泄漏。
綜上, ThreadLocal 內存泄漏的根源是:
由于ThreadLocalMap 的生命周期跟 Thread 一樣長,如果沒有手動刪除對應 key 就會導致內存泄漏.
4、如何避免內存泄漏?
不要聽到「內存泄漏」就不敢使用ThreadLocal,只要規范化使用是不會有問題的。我給大家支幾個招:
1、每次使用完ThreadLocal都記得調用remove()方法清除數據。
2、將ThreadLocal變量盡可能地定義成static final,避免頻繁創建ThreadLocal實例。這樣也就保證程序一直存在ThreadLocal的強引用,也能保證任何時候都能通過ThreadLocal的弱引用訪問到Entry的Value值,進而清除掉。
當然,就是使用不規范,ThreadLocal內部也做了一些優化,比如:
1、調用set()方法時,ThreadLocal會進行采樣清理、全量清理,擴容時還會繼續檢查。
2、調用get()方法時,如果沒有直接命中或者向后環形查找時也會進行清理。
3、調用remove()時,除了清理當前Entry,還會向后繼續清理。
總結
以上是生活随笔為你收集整理的ThreadLocal真的会造成内存泄漏吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从Bitcask存储模型谈超轻量级KV系
- 下一篇: Ynoi