ThreadLocal 內(nèi)存泄露的根本原因

強(qiáng)引用與弱引用

強(qiáng)引用陶耍,使用最普遍的引用奋蔚,一個(gè)對象具有強(qiáng)引用,不會被垃圾回收器回收烈钞。當(dāng)內(nèi)存空間不足泊碑,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤,使程序異常終止毯欣,也不回收這種對象馒过。

如果想取消強(qiáng)引用和某個(gè)對象之間的關(guān)聯(lián),可以顯式地將引用賦值為null酗钞,這樣可以使JVM在合適的時(shí)間就會回收該對象腹忽。

弱引用来累,JVM進(jìn)行垃圾回收時(shí),無論內(nèi)存是否充足窘奏,都會回收被弱引用關(guān)聯(lián)的對象嘹锁。在java中,用java.lang.ref.WeakReference類來表示着裹〖嬉可以在緩存中使用弱引用。

GC回收機(jī)制-如何找到需要回收的對象

JVM找到需要回收的對象的兩種方法

引用計(jì)數(shù)法:每個(gè)對象有一個(gè)引用計(jì)數(shù)屬性求冷,新增一個(gè)引用時(shí)計(jì)數(shù)加1,引用釋放時(shí)計(jì)數(shù)減1窍霞,計(jì)數(shù)為0時(shí)可以回收匠题,
可達(dá)性分析法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱為引用鏈但金。當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連時(shí)韭山,則證明此對象是不可用的,那么虛擬機(jī)就判斷是可回收對象冷溃。
ps:GC 回收的是沒有被應(yīng)用的對象所占的空間(關(guān)鍵字空間)

部分重要的源碼

get()方法調(diào)用ThreadLocalMap的getEntry();
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        // 關(guān)鍵步驟
        return getEntryAfterMiss(key, i, e);
}
 
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
 
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            // 對于key == null的處理钱磅。
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

 清理key為null的元素expungeStaleEntry(i):
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 根據(jù)強(qiáng)引用的取消強(qiáng)引用關(guān)聯(lián)規(guī)則,將value顯式地設(shè)置成null似枕,去除引用
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // 重新hash盖淡,并對table中key為null進(jìn)行處理
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        //對table中key為null進(jìn)行處理,將value設(shè)置為null,清除value的引用
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

ThreadLocalMap的remove()分析
private void remove(ThreadLocal<?> key) {
    //使用hash方式凿歼,計(jì)算當(dāng)前ThreadLocal變量所在table數(shù)組位置
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    //再次循環(huán)判斷是否在為ThreadLocal變量所在table數(shù)組位置
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            //調(diào)用WeakReference的clear方法清除對ThreadLocal的弱引用
            e.clear();
            //清理key為null的元素
            expungeStaleEntry(i);
            return;
        }
    }
}

根據(jù)源碼我們假設(shè)如下場景:

1褪迟、key=ThreadLocal是強(qiáng)引用

強(qiáng)引用圖來自網(wǎng)絡(luò)

2、key = ThreadLocal是弱引用

弱引用圖來自網(wǎng)絡(luò)

總結(jié)

無論 ThreadLocalMap 中的 key 使用哪種類型引用都無法完全避免內(nèi)存泄漏答憔,跟使用弱引用沒有關(guān)系味赃。

要避免內(nèi)存泄漏有兩種方式:

1 .使用完 ThreadLocal ,調(diào)用其 remove 方法刪除對應(yīng)的 Entry
2 .使用完 ThreadLocal 虐拓,當(dāng)前 Thread 也隨之運(yùn)行結(jié)束
相對第一種方式心俗,第二種方式顯然更不好控制,特別是使用線程池的時(shí)候蓉驹,線程結(jié)束是不會銷毀的.
也就是說城榛,只要記得在使用完ThreadLocal 及時(shí)的調(diào)用 remove ,無論 key 是強(qiáng)引用還是弱引用都不會有問題.

由于Thread中包含變量ThreadLocalMap态兴,因此ThreadLocalMap與Thread的生命周期是一樣長吠谢,如果都沒有手動刪除對應(yīng)key,都會導(dǎo)致內(nèi)存泄漏诗茎。

但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內(nèi)存泄漏工坊,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set(),get(),remove()的時(shí)候會被清除献汗。

ThreadLocal正確的使用方法

  • 1 每次使用完ThreadLocal都調(diào)用它的remove()方法清除數(shù)據(jù)
  • 2 將ThreadLocal變量定義成private static,這樣就一直存在ThreadLocal的強(qiáng)引用王污,也就能保證任何時(shí)候都能通過ThreadLocal的弱引用訪問到Entry的value值罢吃,進(jìn)而清除掉 。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昭齐,一起剝皮案震驚了整個(gè)濱河市尿招,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌阱驾,老刑警劉巖就谜,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異里覆,居然都是意外死亡丧荐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門喧枷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虹统,“玉大人,你說我怎么就攤上這事隧甚〕道螅” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵戚扳,是天一觀的道長峭判。 經(jīng)常有香客問我董瞻,道長辽旋,這世上最難降的妖魔是什么歪赢? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮宜雀,結(jié)果婚禮上切平,老公的妹妹穿的比我還像新娘。我一直安慰自己辐董,他們只是感情好悴品,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著简烘,像睡著了一般苔严。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孤澎,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天届氢,我揣著相機(jī)與錄音,去河邊找鬼覆旭。 笑死退子,一個(gè)胖子當(dāng)著我的面吹牛岖妄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寂祥,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荐虐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丸凭?” 一聲冷哼從身側(cè)響起福扬,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惜犀,沒想到半個(gè)月后铛碑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虽界,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年汽烦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浓恳。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖碗暗,靈堂內(nèi)的尸體忽然破棺而出颈将,到底是詐尸還是另有隱情,我是刑警寧澤言疗,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布晴圾,位于F島的核電站,受9級特大地震影響噪奄,放射性物質(zhì)發(fā)生泄漏死姚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一勤篮、第九天 我趴在偏房一處隱蔽的房頂上張望都毒。 院中可真熱鬧,春花似錦碰缔、人聲如沸账劲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瀑焦。三九已至,卻和暖如春梗肝,著一層夾襖步出監(jiān)牢的瞬間榛瓮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工巫击, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留禀晓,地道東北人精续。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像匆绣,于是被迫代替她去往敵國和親驻右。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容