最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談ThreadLocal為什么會內存泄漏

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

前言

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

這里threadLocal為弱引用,(ThreadLocal#ThreadLocalMap#new Entry(threadLocal)產生的弱引用weakRef),value為強引用。 

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

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

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

ThreadLocal原理回顧

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

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

ThreadLocal為什么會內存泄漏

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

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

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

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

來看看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();
}

在調用map.getEntry(this)時,內部會判斷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ù)調用getEntryAfterMiss(key, i, e)方法,其內部回做回收必要的設置,繼續(xù)看內部源碼:

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ù)調用了expungeStaleEntry(i)方法,expunge的意思是擦除,刪除的意思,見名知意,在來看expungeStaleEntry方法的內部實現(xiàn):

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // expunge entry at staleSlot(意思是,刪除value,設置為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;
}

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

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

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

為什么使用弱引用?

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

很多文章大多分析ThreadLocal使用了弱引用會導致內存泄漏,但為什么使用弱引用而不是強引用?

官方文檔的說法:

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

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

key 使用強引用:引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏。

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

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

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

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

總結

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

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

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

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

相關文章

  • Spring的注解簡單介紹

    Spring的注解簡單介紹

    這篇文章主要介紹了Spring的注解簡單介紹,具有一定借鑒價值,需要的朋友可以參考下
    2017-12-12
  • Netty解決 TCP 粘包拆包的方法

    Netty解決 TCP 粘包拆包的方法

    處理粘包的唯一方法就是制定應用層的數(shù)據(jù)通訊協(xié)議,通過協(xié)議來規(guī)范現(xiàn)有接收的數(shù)據(jù)是否滿足消息數(shù)據(jù)的需要,本文給大家介紹Netty解決 TCP 粘包拆包的方法,需要的朋友一起看看吧
    2021-07-07
  • Java并發(fā)之線程池Executor框架的深入理解

    Java并發(fā)之線程池Executor框架的深入理解

    這篇文章主要介紹了Java并發(fā)之線程池Executor框架的深入理解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-11-11
  • Java接口回調和方法回調的簡單實現(xiàn)步驟

    Java接口回調和方法回調的簡單實現(xiàn)步驟

    這篇文章主要介紹了Java接口回調和方法回調的相關資料,接口回調是一種設計模式,實現(xiàn)三方解耦,調用者提供接口實現(xiàn),文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2025-03-03
  • 一文帶你掌握JPA實體類注解

    一文帶你掌握JPA實體類注解

    這篇文章主要給大家詳細介紹一下?JPA?實體類中的注解,文中的示例代碼講解詳細,對我們學習Java有一定的幫助,感興趣的小伙伴可以了解一下
    2023-01-01
  • Java集合中的鏈表與結構詳解

    Java集合中的鏈表與結構詳解

    鏈表是一種物理存儲結構上非連續(xù)的存儲結構,數(shù)據(jù)元素的邏輯順序的通過鏈表中的引用鏈接次序實現(xiàn),文章對比ArrayList與LinkedList的結構差異,詳細講解了鏈表實現(xiàn)方法及常見面試題如反轉、中間節(jié)點查找等,感興趣的朋友跟隨小編一起看看吧
    2025-08-08
  • SpringSecurity退出功能實現(xiàn)的正確方式(推薦)

    SpringSecurity退出功能實現(xiàn)的正確方式(推薦)

    本文將介紹在Spring Security框架下如何實現(xiàn)用戶的"退出"logout的功能。本文通過實例代碼講解的非常詳細,具有一定的參考借鑒價值,需要的朋友參考下吧
    2019-11-11
  • SpringBoot如何實現(xiàn)同域SSO(單點登錄)

    SpringBoot如何實現(xiàn)同域SSO(單點登錄)

    單點登錄(SingleSignOn,SSO),就是通過用戶的一次性鑒別登錄。即在多個應用系統(tǒng)中,只需要登錄一次,就可以訪問其他相互信任的應用系統(tǒng),本文將介紹SpringBoot如何實現(xiàn)同域SSO(單點登錄)
    2021-05-05
  • Springboot集成第三方jar快速實現(xiàn)微信、支付寶等支付場景

    Springboot集成第三方jar快速實現(xiàn)微信、支付寶等支付場景

    這篇文章主要介紹了Springboot集成第三方jar快速實現(xiàn)微信、支付寶等支付場景,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • java定義數(shù)組的三種類型總結

    java定義數(shù)組的三種類型總結

    下面小編就為大家?guī)硪黄猨ava定義數(shù)組的三種類型總結。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-10-10

最新評論

盖州市| 建瓯市| 济南市| 江津市| 通江县| 洞头县| 区。| 孟州市| 布拖县| 木兰县| 林周县| 伊通| 闽侯县| 东兴市| 金坛市| 鹤山市| 铜山县| 禹州市| 正安县| 晋宁县| 杭锦旗| 崇文区| 稷山县| 丽江市| 呼玛县| 荥阳市| 石景山区| 浦北县| 吴忠市| 大新县| 余姚市| 班戈县| 邯郸县| 商水县| 广灵县| 武胜县| 芜湖县| 贞丰县| 开化县| 射洪县| 和林格尔县|