一、前言
對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());
}
}
指向關系如下所示,主線程就不畫了,各位别嫌棄圖的配色,我真的是配不出來了。
在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而引起的記憶體洩露。