ThreadLocal使用不好,小心造成內存泄露!

一、前言

對ThreadLocal不熟悉的同學,可以先參考我的另外一篇文章淺談ThreadLocal

在討論內存泄漏之前,需要明白java中的四種引用,同樣可以移步到java中的四種引用

什麼是內存泄露?

大白話講,就是我自己創建的對象,在一系列操作後,我訪問不到該對象了,我認爲它已經被回收掉了,但該對象卻一直存在與內存中。


二、示例

先給出一個簡單例子,用來說明引用與對象的指向關係

package com.yang.testThreadLocal;

public class Main {

    private static ThreadLocal<Integer> tl = new ThreadLocal<>();

    public static void main(String[] args) {

        tl.set(1);

        Thread t = new Thread(() -> {
            tl.set(2);
            System.out.println("子線程:" + tl.get());
        });
        t.start();

        System.out.println("主線程:" + tl.get());

    }
}

指向關係如下所示,主線程就不畫了,各位別嫌棄圖的配色,我真的是配不出來了。

在ThreadLocalMap中,維護一個Entry類型的數組,Entry是一個(k,v)結構,可以把ThreadLocalMap理解爲一個定製化的HashMap。不過Entry的key是對ThreadLocal的一個弱引用,在執行tl=null後,1號線斷開,則該ThreadLocal會在下一次GC到來的時候,被回收掉。

爲什麼這裏的key保持着對ThreadLocal的一個弱引用呢?保持強引用行不行?

假設這裏的key保持對ThreadLocal的強引用,則當我的程序用不到該ThreadLocal時,我手動執行了tl=null,此時1號線斷開,而這裏的5號線是實線,5號線沒有斷開,因此ThreadLocal對象無法被回收掉,一直存在於內存中,造成內存泄露。

看來,這裏的弱引用,能夠保證用不到的ThreadLocal被回收掉。

弱引用就能完全防止內存泄露了嗎?

由上面的分析,弱引用能夠防止釋放不掉ThreadLocal引起的內存泄露。但是,卻不能防止釋放不掉Integer引起的內存泄露。首先,執行tl=null,則1號線斷開,GC到來時,5號線斷開,此時ThreadLocal被回收掉了,這個key被置爲了null,可是這個key對應的value強引用着Integer對象,該Integer無法在用戶代碼中訪問到了,但卻依然存在於內存中,造成內存泄露。

既然依然存在着內存泄露,那麼JDK團隊是怎麼解決的呢?

其實,ThreadLocal中的get()、set()方法,不是單純地去做獲取、設置的操作。在它們的方法內部,依然會遍歷該Entry數組,刪除所有key爲null的Entry,並將相關的value置爲null,從而夠解決因釋放不掉value而引起的內存泄露。

有這些get()、set()方法,就能完全地防止內存泄漏嗎?

但我們手動將tl置爲null後,就已經沒法調用這些get()、set()方法了。所以,預防內存泄露的最佳實踐是,在使用完ThreadLocal後,先調用tl.remove(),再調用tl=null。tl.remove()能夠使得ThreadLocalMap刪除該ThreadLocal所在的Entry,以及將value置爲null,tl=null使得ThreadLocal對象真正地被回收掉。

 

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