天天看點

ThreadLocal使用不好,小心造成記憶體洩露!

一、前言

對ThreadLocal不熟悉的同學,可以先參考我的

在讨論記憶體洩漏之前,需要明白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());

    }
}      

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

ThreadLocal使用不好,小心造成記憶體洩露!

在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而引起的記憶體洩露。

繼續閱讀