欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談ThreadLocal為什么會內(nèi)存泄漏

 更新時間:2023年12月01日 10:33:31   作者:thewindkee  
這篇文章主要介紹了淺談ThreadLocal為什么會內(nèi)存泄漏,每個Thread內(nèi)部維護(hù)著一個ThreadLocalMap,它是一個Map,這個映射表的Key是一個弱引用,其實就是ThreadLocal本身,Value是真正存的線程變量Object,需要的朋友可以參考下

前言

如果線程使用線程池或者Thread長時間不會消亡,其內(nèi)部的threadLocalMap也一直存在。而thread.threadLocalMap.set(threadLocal,value)。 

這里threadLocal為弱引用,(ThreadLocal#ThreadLocalMap#new Entry(threadLocal)產(chǎn)生的弱引用weakRef),value為強(qiáng)引用。 

Entry中弱引用key對應(yīng)的threadLocal  會在gc的時候 回收,因此value對應(yīng)的key會變成null.value對應(yīng)的內(nèi)存就無法再被訪問,已經(jīng)泄露了。

不過好在threadLocal中 expungeStaleEntry(threadLocal調(diào)用get/set/remove觸發(fā)) 會清除key為null的value,一定程度解決了內(nèi)存泄漏的問題。

ps:當(dāng)threadLocal 不為靜態(tài)變量,且被回收的時候才會導(dǎo)致weakRef為null。

ThreadLocal原理回顧

ThreadLocal的原理:每個Thread內(nèi)部維護(hù)著一個ThreadLocalMap,它是一個Map。這個映射表的Key是一個弱引用,其實就是ThreadLocal本身,Value是真正存的線程變量Object。

也就是說ThreadLocal本身并不真正存儲線程的變量值,它只是一個工具,用來維護(hù)Thread內(nèi)部的Map,幫助存和取。注意上圖的虛線,它代表一個弱引用類型,而弱引用的生命周期只能存活到下次GC前。

ThreadLocal為什么會內(nèi)存泄漏

ThreadLocal在ThreadLocalMap中是以一個弱引用身份被Entry中的Key引用的,因此如果ThreadLocal沒有外部強(qiáng)引用來引用它,那么ThreadLocal會在下次JVM垃圾收集時被回收。

這個時候就會出現(xiàn)Entry中Key已經(jīng)被回收,出現(xiàn)一個null Key的情況,外部讀取ThreadLocalMap中的元素是無法通過null Key來找到Value的。

因此如果當(dāng)前線程的生命周期很長,一直存在,那么其內(nèi)部的ThreadLocalMap對象也一直生存下來,這些null key就存在一條強(qiáng)引用鏈的關(guān)系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,這條強(qiáng)引用鏈會導(dǎo)致Entry不會回收,Value也不會回收,但Entry中的Key卻已經(jīng)被回收的情況,造成內(nèi)存泄漏。

但是JVM團(tuán)隊已經(jīng)考慮到這樣的情況,并做了一些措施來保證ThreadLocal盡量不會內(nèi)存泄漏:在ThreadLocal的get()、set()、remove()方法調(diào)用的時候會清除掉線程ThreadLocalMap中所有Entry中Key為null的Value,并將整個Entry設(shè)置為null,利于下次內(nèi)存回收。

來看看ThreadLocal的get()方法底層實現(xiàn)

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

在調(diào)用map.getEntry(this)時,內(nèi)部會判斷key是否為null,繼續(xù)看map.getEntry(this)源碼

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
        return getEntryAfterMiss(key, i, e);
}

在getEntry方法中,如果Entry中的key發(fā)現(xiàn)是null,會繼續(xù)調(diào)用getEntryAfterMiss(key, i, e)方法,其內(nèi)部回做回收必要的設(shè)置,繼續(xù)看內(nèi)部源碼:

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)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

注意k == null這里,繼續(xù)調(diào)用了expungeStaleEntry(i)方法,expunge的意思是擦除,刪除的意思,見名知意,在來看expungeStaleEntry方法的內(nèi)部實現(xiàn):

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // expunge entry at staleSlot(意思是,刪除value,設(shè)置為null便于下次回收)
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;
    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

注意這里,將當(dāng)前Entry刪除后,會繼續(xù)循環(huán)往下檢查是否有key為null的節(jié)點(diǎn),如果有則一并刪除,防止內(nèi)存泄漏。

但這樣也并不能保證ThreadLocal不會發(fā)生內(nèi)存泄漏,例如:

使用static的ThreadLocal,延長了ThreadLocal的生命周期,可能導(dǎo)致的內(nèi)存泄漏。分配使用了ThreadLocal又不再調(diào)用get()、set()、remove()方法,那么就會導(dǎo)致內(nèi)存泄漏。

為什么使用弱引用?

從表面上看,發(fā)生內(nèi)存泄漏,是因為Key使用了弱引用類型。但其實是因為整個Entry的key為null后,沒有主動清除value導(dǎo)致。

很多文章大多分析ThreadLocal使用了弱引用會導(dǎo)致內(nèi)存泄漏,但為什么使用弱引用而不是強(qiáng)引用?

官方文檔的說法:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. 為了處理非常大和生命周期非常長的線程,哈希表使用弱引用作為 key。

下面我們分兩種情況討論:

key 使用強(qiáng)引用:引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用,如果沒有手動刪除,ThreadLocal不會被回收,導(dǎo)致Entry內(nèi)存泄漏。

key 使用弱引用:引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。

value在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。

比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動刪除對應(yīng)key,都會導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內(nèi)存泄漏,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。

因此,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應(yīng)key的value就會導(dǎo)致內(nèi)存泄漏,而不是因為弱引用。

總結(jié)

綜合上面的分析,我們可以理解ThreadLocal內(nèi)存泄漏的前因后果,那么怎么避免內(nèi)存泄漏呢?

每次使用完ThreadLocal,都調(diào)用它的remove()方法,清除數(shù)據(jù)。

在使用線程池的情況下,沒有及時清理ThreadLocal,不僅是內(nèi)存泄漏的問題,更嚴(yán)重的是可能導(dǎo)致業(yè)務(wù)邏輯出現(xiàn)問題。所以,使用ThreadLocal就跟加鎖完要解鎖一樣,用完就清理。

到此這篇關(guān)于淺談ThreadLocal為什么會內(nèi)存泄漏的文章就介紹到這了,更多相關(guān)ThreadLocal內(nèi)存泄漏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java中 String和StringBuffer的區(qū)別實例詳解

    java中 String和StringBuffer的區(qū)別實例詳解

    這篇文章主要介紹了java中 String和StringBuffer的區(qū)別實例詳解的相關(guān)資料,一個小的例子,來測試String和StringBuffer在時間和空間使用上的差別,需要的朋友可以參考下
    2017-04-04
  • java開發(fā)工作中對InheritableThreadLocal使用思考

    java開發(fā)工作中對InheritableThreadLocal使用思考

    這篇文章主要為大家介紹了java開發(fā)工作中對InheritableThreadLocal使用思考詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • java中把字符串轉(zhuǎn)成 double的方法

    java中把字符串轉(zhuǎn)成 double的方法

    Java 中可以使用 Double 類中的靜態(tài)方法 parseDouble() 將一個字符串轉(zhuǎn)換為 double 類型的數(shù)值,本文結(jié)合實例代碼對java字符串轉(zhuǎn)成 double詳細(xì)講解,需要的朋友參考下吧
    2023-08-08
  • SpringMvc中的Bean加載機(jī)制詳解

    SpringMvc中的Bean加載機(jī)制詳解

    這篇文章主要介紹了SpringMvc中的Bean加載機(jī)制詳解,在Spring MVC中,Bean的作用主要是處理應(yīng)用程序的業(yè)務(wù)邏輯和數(shù)據(jù),例如,一個用戶管理應(yīng)用程序的Bean可能包括UserService、UserDao和UserController等,需要的朋友可以參考下
    2023-12-12
  • Java讀寫Windows共享文件夾的方法實例

    Java讀寫Windows共享文件夾的方法實例

    本篇文章主要介紹了Java讀寫Windows共享文件夾的方法實例,具有一定的參考價值,有興趣的同學(xué)可以了解一下。
    2016-11-11
  • Java發(fā)送http請求的示例(get與post方法請求)

    Java發(fā)送http請求的示例(get與post方法請求)

    這篇文章主要介紹了Java發(fā)送http請求的示例(get與post方法請求),幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2021-01-01
  • java使用Feign實現(xiàn)聲明式Restful風(fēng)格調(diào)用

    java使用Feign實現(xiàn)聲明式Restful風(fēng)格調(diào)用

    這篇文章主要為大家詳細(xì)介紹了java使用Feign實現(xiàn)聲明式Restful風(fēng)格調(diào)用,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-04-04
  • SpringBoot單元測試之?dāng)?shù)據(jù)隔離詳解

    SpringBoot單元測試之?dāng)?shù)據(jù)隔離詳解

    我們在寫單元測試時,有一個比較重要的要求是可以重復(fù)運(yùn)行, 那么這樣就會有一個比較麻煩的問題:數(shù)據(jù)污染,所以本文為大家整理了兩個數(shù)據(jù)隔離的方式,希望對大家有所幫助
    2023-08-08
  • 利用Log4j將不同Package的日志輸出到不同文件的方法

    利用Log4j將不同Package的日志輸出到不同文件的方法

    日志是應(yīng)用軟件中不可缺少的部分,Apache的開源項目log4j是一個功能強(qiáng)大的日志組件,提供方便的日志記錄。這篇文章主要介紹了利用Log4j將不同Package的日志輸出到不同文件的方法,需要的朋友可以參考借鑒,下面來跟著小編一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-01-01
  • 關(guān)于protected修飾符詳解-源于Cloneable接口

    關(guān)于protected修飾符詳解-源于Cloneable接口

    這篇文章主要介紹了protected修飾符詳解-源于Cloneable接口,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評論