詳解ThreadLocal爲何存在內存泄漏

ThreadLocal是Java中用於保證線程安全的一種措施,通過給每個線程分配一個專屬的值存儲空間,保證線程各自維護自己的變量,從而不會發生併發訪問問題。

但是ThreadLocal是存在着內存泄漏風險的,如果使用不當,容易發生memory leak錯誤。

首先解釋什麼是內存泄漏。

內存泄漏memory leak :是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄漏似乎不會有大的影響,但內存泄漏堆積後的後果就是內存溢出。

線程Thread對象中,每個線程對象內部都有一個的ThreadLocalMap對象。如果這個對象存儲了多個大對象,則可能早出內存溢出OOM。

爲了防止這種情況發生,在ThreadLocal的源碼中,有對應的策略,即調用 get()、set()、remove() 方法,均會清除 ThreadLocal內部的 內存。

ThreadLocal的內部是ThreadLocalMap。ThreadLocalMap內部是由一個Entry數組組成。Entry類的構造函數爲 Entry(弱引用的ThreadLocal對象, Object value對象)。因爲Entry的key是一個弱引用的ThreadLocal對象,所以在 垃圾回收 之前,將會清除此Entry對象的key。那麼, ThreadLocalMap 中就會出現 key 爲 null 的 Entry,就沒有辦法訪問這些 key 爲 null 的 Entry 的 value。這些 value 被Entry對象引用,所以value所佔內存不會被釋放。若在指定的線程任務裏面,調用ThreadLocal對象的get()、set()、remove()方法,可以避免出現內存泄露。

ThreadLocal對象被GC回收了,那麼key變成了null。Map又是通過key拿到的value的對象。所以,GC在回收了key所佔內存後,沒法訪問到value的值,因爲需要通過key才能訪問到value對象。另外,如圖所示的引用鏈:CurrentThread – Map – Entry – value ,所以,在當前線程沒有被回收的情況下,value所佔內存也不會被回收。所以可能會造成了內存溢出。

弱引用只要繼承WeakReference類即可。所以說,當ThreadLocal對象被GC回收了以後,Entry對象的key就變成null了。這個時候沒法訪問到 Object Value了。並且最致命的是,Entry持有Object value。所以,value的內存將不會被釋放。

因爲上述的原因,在ThreadLocal這個類的get()、set()、remove()方法,均有實現回收 key 爲 null 的 Entry 的 value所佔的內存。所以,爲了防止內存泄露(沒法訪問到的內存),在不會再用ThreadLocal的線程任務末尾,調用一次 上述三個方法的其中一個即可。

因此,可以理解到爲什麼JDK源碼中要把Entry對象,用 弱引用的ThreadLocal對象,設計爲key,那是因爲要手動編寫代碼釋放ThreadLocalMap中 key爲null的Entry對象。

那麼GC什麼時候回收弱引用的對象?弱引用對象是存活到下一次垃圾回收發生之前對象。

綜上:JVM會自動回收某些對象將其置爲null,從而避免OutOfMemory的錯誤。弱引用的對象可以被JVM設置爲null。我們的代碼通過判斷key是否爲null,從而 手動釋放 內存泄露的內存。

爲什麼要將ThreadLocal設計爲弱引用?

答:因爲弱引用的對象的生命週期直到下一次垃圾回收之前被回收。弱引用的對象將會被置爲null。我們可以通過判斷弱引用對象是否已經爲null,來進行相關的操作。在ThreadLocalMap中,如果鍵ThreadLocal已經被回收,說明ThreadLocal對象已經爲null,所以其對應的值已經無法被訪問到。這個時候,需要及時手動編寫代碼清理掉這個鍵值對,防止內存泄露導致的內存溢出。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
}

強引用: 不會被回收的內存。

軟引用: 內部不足的時候回收的內存。

弱引用: 存活到垃圾回收前的內存。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章