日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

阿里二面:談談ThreadLocal的內存泄漏問題?

來源: 責編: 時間:2024-03-22 08:53:21 247觀看
導讀引言ThreadLocal在Java多線程編程中扮演著重要的角色,它提供了一種線程局部存儲機制,允許每個線程擁有獨立的變量副本,從而有效地避免了線程間的數據共享沖突。ThreadLocal的主要用途在于,當需要為每個線程維護一個獨立的

引言

ThreadLocal在Java多線程編程中扮演著重要的角色,它提供了一種線程局部存儲機制,允許每個線程擁有獨立的變量副本,從而有效地避免了線程間的數據共享沖突。ThreadLocal的主要用途在于,當需要為每個線程維護一個獨立的上下文變量時,比如每個線程的事務ID、用戶登錄信息、數據庫連接等,可以減少對同步機制如synchronized關鍵字或Lock類的依賴,提高系統的執行效率和簡化代碼邏輯。DYc28資訊網——每日最新資訊28at.com

但是我們在使用ThreadLocal時,經常因為使用不當導致內存泄漏。此時就需要我們去探究一下ThreadLocal在哪些場景下會出現內存泄露?哪些場景下不會出現內存泄露?出現內存泄露的根本原因又是什么呢?如何避免內存泄露?DYc28資訊網——每日最新資訊28at.com

ThreadLocal原理

ThreadLocal的實現基于每個線程內部維護的一個ThreadLocalMap。DYc28資訊網——每日最新資訊28at.com

public class Thread implements Runnable {     /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;}

ThreadLocalMap是ThreadLocal類的一個靜態內部類,ThreadLocal本身不能存儲數據,它在作用上更像一個工具類,ThreadLocal類提供了set(T value)、get()等方法來操作ThreadLocalMap存儲數據。DYc28資訊網——每日最新資訊28at.com

public class ThreadLocal<T> {    // ...    public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }    public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        return setInitialValue();    }    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }    // ...}

而ThreadLocalMap內部維護了一個Entry數據,用來存儲數據,Entry繼承了WeakReference,所以Entry的key是一個弱引用,可以被GC回收。Entry數組中的每一個元素都是一個Entry對象。每個Entry對象中存儲著一個ThreadLocal對象與其對應的value值。DYc28資訊網——每日最新資訊28at.com

static class ThreadLocalMap {    static class Entry extends WeakReference<ThreadLocal<?>> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal<?> k, Object v) {                super(k);                value = v;            }        }}

關于弱引用的知識點,請參考:DYc28資訊網——每日最新資訊28at.com

而Entry數組中Entry對象的下標位置是通過ThreadLocal的threadLocalHashCode計算出來的。DYc28資訊網——每日最新資訊28at.com

private ThreadLocalMap(ThreadLocalMap parentMap) {    Entry[] parentTable = parentMap.table;    int len = parentTable.length;    setThreshold(len);    table = new Entry[len];    for (Entry e : parentTable) {        if (e != null) {            @SuppressWarnings("unchecked")            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();            if (key != null) {                Object value = key.childValue(e.value);                Entry c = new Entry(key, value);                // 通過key的threadLocalHashCode計算下標,這個key就是ThreadLocall對象                int h = key.threadLocalHashCode & (len - 1);                while (table[h] != null)                    h = nextIndex(h, len);                table[h] = c;                size++;            }        }    }}

而從Entry數組中獲取對應key即ThreadLocal對應的value值時,也是通過key的threadLocalHashCode計算下標,從而可以快速的返回對應的Entry對象。DYc28資訊網——每日最新資訊28at.com

private Entry getEntry(ThreadLocal<?> key) {// 通過key的threadLocalHashCode計算下標,這個key就是ThreadLocall對象    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);}

在Thread中,可以存儲多個ThreadLocal對象。Thread、ThreadLocal、ThreadLocalMap以及Entry數組的關系如下圖:DYc28資訊網——每日最新資訊28at.com

圖片圖片DYc28資訊網——每日最新資訊28at.com

ThreadLocal在哪些場景下不會出現內存泄露?

當一個對象失去所有強引用,或者它僅被弱引用、軟引用、虛引用關聯時,垃圾收集器(GC)通常都能識別并回收這些對象,從而避免內存泄漏的發生。當我們在手動創建線程時,若將變量存儲到ThreadLocal中,那么在Thread線程正常運行的過程中,它會維持對內部ThreadLocalMap實例的引用。只要該Thread線程持續執行任務,這種引用關系將持續存在,確保ThreadLocalMap實例及其中存儲的變量不會因無引用而被GC回收。DYc28資訊網——每日最新資訊28at.com

圖片圖片DYc28資訊網——每日最新資訊28at.com

當線程執行完任務并正常退出后,線程與內部ThreadLocalMap實例之間的強引用關系隨之斷開,這意味著線程不再持有ThreadLocalMap的引用。在這種情況下,失去強引用的ThreadLocalMap對象將符合垃圾收集器(GC)的回收條件,進而被自動回收。與此同時,鑒于ThreadLocalMap內部的鍵(ThreadLocal對象)是弱引用,一旦ThreadLocalMap被回收,若此時沒有其他強引用指向這些ThreadLocal對象,它們也將被GC一并回收。因此,在線程結束其生命周期后,與之相關的ThreadLocalMap及其包含的ThreadLocal對象理論上都能夠被正確清理,避免了內存泄漏問題。DYc28資訊網——每日最新資訊28at.com

實際應用中還需關注ThreadLocalMap中存儲的值(非鍵)是否為強引用類型,因為即便鍵(ThreadLocal對象)被回收,如果值是強引用且沒有其他途徑釋放,仍可能導致內存泄漏。DYc28資訊網——每日最新資訊28at.com

ThreadLocal在哪些場景下會出現內存泄露?

在實際項目開發中,如果為每個任務都手動創建線程,這是一件很耗費資源的方式,并且在阿里巴巴的開發規范中也提到,不推薦使用手動創建線程,推薦使用線程池來執行相對應的任務。那么當我們使用線程池時,線程池中的線程跟ThrealLocalMap的引用關系如下:DYc28資訊網——每日最新資訊28at.com

圖片圖片DYc28資訊網——每日最新資訊28at.com

在使用線程池處理任務時,每一個線程都會關聯一個獨立的ThreadLocalMap對象,用于存儲線程本地變量。由于線程池中的核心線程在完成任務后不會被銷毀,而是保持活動狀態等待接收新的任務,這意味著核心線程與其內部持有的ThreadLocalMap對象之間始終保持著強引用關系。因此,只要核心線程存活,其所對應的ThreadLocal對象和ThreadLocalMap不會被垃圾收集器(GC)自動回收,此時就會存在內存泄露的風險。DYc28資訊網——每日最新資訊28at.com

關于Java中的線程池參數以及原理,請參考:重溫Java基礎(二)之Java線程池最全詳解DYc28資訊網——每日最新資訊28at.com

出現內存泄露的根本原因

由上述ThreadLocalMap的結構圖以及ThreadLocalMap的源碼中,我們知道ThreadLocalMap中包含一個Entry數組,而Entry數組中的每一個元素就是Entry對象,Entry對象中存儲的Key就是ThreadLocal對象,而value就是要存儲的數據。其中,Entry對象中的Key屬于弱引用。DYc28資訊網——每日最新資訊28at.com

static class ThreadLocalMap {    static class Entry extends WeakReference<ThreadLocal<?>> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal<?> k, Object v) {                super(k);                value = v;            }        }}

而對于弱引用WeakReference,在引用的對象使用完畢之后,即使內存足夠,GC也會對其進行回收。DYc28資訊網——每日最新資訊28at.com

關于弱引用的知識點,請參考:DYc28資訊網——每日最新資訊28at.com

圖片圖片DYc28資訊網——每日最新資訊28at.com

當Entry對象中的Key被GC自動回收后,對應的ThreadLocal被GC回收掉了,變成了null,但是ThreadLocal對應的value值依然被Entry引用,不能被GC自動回收。這樣就造成了內存泄漏的風險。DYc28資訊網——每日最新資訊28at.com

圖片圖片DYc28資訊網——每日最新資訊28at.com

在線程池環境下使用ThreadLocal存儲數據時,內存泄露的風險主要源自于線程生命周期管理及ThreadLocalMap內部結構的設計。由于線程池中的核心線程在完成任務后會復用,每個線程都會維持對各自關聯的ThreadLocalMap對象的強引用,這確保了只要線程持續存在,其對應的ThreadLocalMap就無法被垃圾收集器(GC)自動回收。DYc28資訊網——每日最新資訊28at.com

進一步分析,ThreadLocalMap內部采用一個Entry數組來保存鍵值對,其中每個條目的Key是當前線程中對應ThreadLocal實例的弱引用,這意味著當外部不再持有該ThreadLocal實例的強引用時,Key部分能夠被GC正常回收。然而,關鍵在于Entry的Value部分,它直接或間接地持有著強引用的對象,即使Key因為弱引用特性被回收,但Value所引用的數據卻不會隨之釋放,除非明確移除或者整個ThreadLocalMap隨著線程結束而失效。DYc28資訊網——每日最新資訊28at.com

所以,在線程池中,如果未正確清理不再使用的ThreadLocal變量,其所持有的強引用數據將在多個任務執行過程中逐漸積累并駐留在線程的ThreadLocalMap中,從而導致潛在的內存泄露風險。DYc28資訊網——每日最新資訊28at.com

ThreadLocal如何避免內存泄漏

經過上述ThreadLocal原理以及發生內存泄漏的分析,我們知道防止內存泄漏,我們一定要在完成線程內的任務后,調用ThreadLocal的remove()方法來清除當前線程中ThreadLocal所對應的值。其remove方法源碼如下:DYc28資訊網——每日最新資訊28at.com

public void remove() {     ThreadLocalMap m = getMap(Thread.currentThread());     if (m != null) {         m.remove(this);     } }

在remove()方法中,首先根據當前線程獲取ThreadLocalMap類型的對象,如果不為空,則直接調用該對象的有參remove()方法移除value的值。ThreadLocalMap的remove方法源碼如下:DYc28資訊網——每日最新資訊28at.com

private void remove(ThreadLocal<?> key) {    Entry[] tab = table;    int len = tab.length;    int i = key.threadLocalHashCode & (len-1);    for (Entry e = tab[i];         e != null;         e = tab[i = nextIndex(i, len)]) {        if (e.get() == key) {            e.clear();            expungeStaleEntry(i);            return;        }    }}

由上述ThreadLocalMap中的set()方法知道ThreadLocal中Entry下標是通過計算ThreadLocal的hashCode獲得了,而remove()方法要找到需要移除value所在Entry數組中的下標時,也時通過當前ThreadLocal對象的hashCode獲的,然后找到它的下標之后,調用expungeStaleEntry將其value也置為null。我們繼續看一下expungeStaleEntry方法的源碼:DYc28資訊網——每日最新資訊28at.com

private int expungeStaleEntry(int staleSlot) {    Entry[] tab = table;    int len = tab.length;    // expunge entry at staleSlot    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;}

在expungeStaleEntry()方法中,會將ThreadLocal為null對應的value設置為null,同時會把對應的Entry對象也設置為null,并且會將所有ThreadLocal對應的value為null的Entry對象設置為null,這樣就去除了強引用,便于后續的GC進行自動垃圾回收,也就避免了內存泄露的問題。即調用完remove方法之后,ThreadLocalMap的結構圖如下:DYc28資訊網——每日最新資訊28at.com

image.pngDYc28資訊網——每日最新資訊28at.com

在ThreadLocal中,不僅僅是remove()方法會調用expungeStaleEntry()方法,在set()方法和get()方法中也可能會調用expungeStaleEntry()方法來清理數據。這種設計確保了即使沒有顯式調用remove()方法,系統也會在必要時自動清理不再使用的ThreadLocal變量占用的內存資源。DYc28資訊網——每日最新資訊28at.com

需要我們特別注意的是,盡管ThreadLocal提供了remove這種機制來防止內存泄漏,但它并不會自動執行相關的清理操作。所以為了確保資源有效釋放并避免潛在的內存泄露問題,我們應當在完成對ThreadLocal對象中數據的使用后,及時調用其remove()方法。我們最好(也是必須)是在try-finally代碼塊結構中,在finally塊中明確地執行remove()方法,這樣即使在處理過程中拋出異常,也能確保ThreadLocal關聯的數據被清除,從而有利于GC回收不再使用的內存空間,避免內存泄漏。DYc28資訊網——每日最新資訊28at.com

總結

本文探討了ThreadLocal的工作原理以及其內存泄漏問題及解決策略。ThreadLocal通過為每個線程提供獨立的變量副本,實現多線程環境下的數據隔離。其內部通過ThreadLocalMap與當前線程綁定,利用弱引用管理鍵值對。但是,如果未及時清理不再使用的ThreadLocal變量,可能導致內存泄漏,尤其是在線程池場景下。解決辦法包括在完成任務后調用remove方法移除無用數據。正確理解和使用ThreadLocal能夠有效提升并發編程效率,但務必關注潛在的內存泄漏風險。DYc28資訊網——每日最新資訊28at.com

DYc28資訊網——每日最新資訊28at.com

本文鏈接:http://m.www897cc.com/showinfo-26-78498-0.html阿里二面:談談ThreadLocal的內存泄漏問題?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 復盤!如何設計可視化搭建平臺的組件商店?

下一篇: 定制JSON轉換:探索.NET Core中JsonSerializerOptions的秘密

標簽:
  • 熱門焦點
Top 日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不
欧美日韩天堂| 午夜精品久久99蜜桃的功能介绍| 国产综合网站| 精品电影一区| 亚洲人午夜精品| 在线视频欧美日韩| 午夜在线不卡| 老牛国产精品一区的观看方式| 欧美~级网站不卡| 欧美精品一区二区三区在线播放| 国产精品mm| 国产自产精品| 日韩一级大片| 羞羞答答国产精品www一本| 久久天天躁狠狠躁夜夜爽蜜月| 欧美成人在线网站| 国产精品成人一区二区三区夜夜夜 | 国产精品久久久久久久久久直播| 国产日韩亚洲欧美| 亚洲经典在线| 午夜在线成人av| 欧美国产精品久久| 国产日韩精品综合网站| 亚洲日本aⅴ片在线观看香蕉| 亚洲永久免费精品| 牛牛国产精品| 国产精品入口尤物| 亚洲国产一二三| 欧美一区二区三区四区夜夜大片| 欧美国产三级| 国产一区二区在线观看免费播放| 亚洲另类一区二区| 久久福利精品| 欧美视频一区二区三区| 亚洲国产乱码最新视频| 欧美一级理论片| 欧美日韩天天操| 亚洲电影在线免费观看| 午夜精品影院| 欧美日韩午夜剧场| 亚洲福利专区| 性久久久久久| 欧美视频免费在线观看| 亚洲国产一区二区三区青草影视| 欧美一级理论性理论a| 欧美三级视频在线| 亚洲国产另类久久精品| 久久成人精品无人区| 欧美天堂亚洲电影院在线观看 | 国内精品福利| 亚洲欧美在线观看| 欧美女激情福利| 亚洲福利视频网| 久久精品国产久精国产爱| 国产精品第一页第二页第三页| 亚洲国产精品成人va在线观看| 欧美一区二区三区男人的天堂| 欧美视频中文一区二区三区在线观看| 亚洲第一在线综合网站| 久久精品亚洲乱码伦伦中文| 国产精品视屏| 亚洲一区二区三区四区五区黄| 欧美激情第10页| 在线看一区二区| 久久久噜噜噜久久| 国产一区二区日韩精品| 午夜精品久久久久久99热软件| 欧美视频导航| 亚洲深夜福利网站| 欧美日韩无遮挡| 亚洲精品乱码久久久久久蜜桃91| 久久夜色精品一区| 国内揄拍国内精品少妇国语| 欧美在线一区二区| 国产区亚洲区欧美区| 午夜精彩视频在线观看不卡 | 欧美激情一区二区三区在线视频| 亚洲第一在线| 久热国产精品| 在线国产精品播放| 免费成人性网站| 亚洲国产精品久久久久久女王| 久久综合中文| 亚洲电影在线播放| 暖暖成人免费视频| 亚洲激情社区| 欧美激情中文不卡| 99国产精品视频免费观看| 欧美久久久久久久久| 99re8这里有精品热视频免费| 欧美伦理在线观看| 一区二区三区免费观看| 欧美三区视频| 亚洲欧美日韩第一区| 国产欧美日韩综合精品二区| 午夜精品理论片| 国产亚洲欧美一区在线观看| 久久久精品国产免大香伊| 一区在线影院| 欧美高清视频| 一区二区三区视频在线观看| 国产精品久久久亚洲一区| 欧美一区二区视频97| 狠狠久久婷婷| 欧美成人免费小视频| 一本大道久久精品懂色aⅴ| 国产精品国产精品| 久久国产高清| 亚洲国产毛片完整版| 欧美日韩国产限制| 亚洲自拍偷拍麻豆| 国内精品嫩模av私拍在线观看| 麻豆av一区二区三区久久| 亚洲精品一区在线观看香蕉| 国产精品www色诱视频| 欧美在线亚洲一区| 亚洲国产精选| 国产精品v欧美精品v日韩| 欧美专区在线观看一区| 亚洲国产精品传媒在线观看| 欧美日韩精品欧美日韩精品一| 亚洲欧美国产精品va在线观看| 韩国三级电影久久久久久| 欧美激情网友自拍| 亚洲女爱视频在线| 精品成人免费| 欧美日韩精品二区| 欧美一级淫片aaaaaaa视频| 亚洲高清在线播放| 国产精品久久国产三级国电话系列| 久久精品30| 99视频有精品| 国产综合色一区二区三区 | 国产精品久久久久aaaa| 久久久久久一区二区| 99热免费精品在线观看| 国产毛片精品国产一区二区三区| 鲁大师成人一区二区三区| 在线性视频日韩欧美| 国语对白精品一区二区| 欧美日韩成人| 久久精品在线视频| 日韩亚洲欧美成人一区| 国产日韩亚洲欧美综合| 欧美精品久久久久久| 欧美一区二区日韩| 日韩午夜av在线| 国产一区再线| 欧美亚男人的天堂| 免费人成网站在线观看欧美高清| 亚洲综合色网站| 亚洲全黄一级网站| 国产亚洲欧美另类中文 | 国产精品国产三级国产aⅴ入口| 久久噜噜亚洲综合| 亚洲伊人久久综合| 亚洲精品国偷自产在线99热| 国内精品福利| 国产精品一二一区| 欧美人成在线视频| 久久亚洲国产成人| 亚洲欧美一区二区视频| 亚洲美女中文字幕| 亚洲第一主播视频| 国产亚洲精品aa午夜观看| 欧美日韩综合视频网址| 女女同性精品视频| 久久九九精品99国产精品| 亚洲中字在线| 一区二区三区日韩欧美| 91久久久亚洲精品| 国内激情久久| 国产一区二区福利| 国产精品视频自拍| 国产精品福利在线| 欧美日韩三级| 欧美精品久久久久久久免费观看 | 另类天堂视频在线观看| 久久精品国产91精品亚洲| 亚洲一区尤物| 一区二区三区国产在线| 亚洲欧洲日产国码二区| 在线不卡中文字幕播放| 国产中文一区| 国产日产亚洲精品| 国产精品一区二区久久| 国产精品成人va在线观看| 欧美久久久久久久久| 欧美va天堂| 欧美 日韩 国产精品免费观看| 久久另类ts人妖一区二区| 久久久精品欧美丰满| 久久福利一区| 欧美一区二区日韩| 篠田优中文在线播放第一区| 亚洲欧美日韩精品久久亚洲区| 亚洲午夜精品久久| 中日韩美女免费视频网址在线观看 | 欧美制服丝袜第一页| 亚洲综合成人在线| 每日更新成人在线视频| 国内精品美女在线观看| 欧美日韩三级在线| 另类春色校园亚洲|