成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

ThreadLocal源碼解讀:內(nèi)存泄露問題分析

開發(fā) 前端
ThreadLocal 優(yōu)勢是無鎖化提升并發(fā)性能和簡化變量的傳遞邏輯。在實(shí)際業(yè)務(wù)中使用 ThreadLocal 類時(shí)應(yīng)該在恰當(dāng)位置調(diào)用 remove 方法顯式移除值。盡可能的避免觸發(fā) ThreadLocal 清理過時(shí) Entry 的邏輯,從而提高 ThreadLocal 性能。

引言

大家好,我們又見面了。今天依舊是結(jié)合源碼為大家分享個(gè)人對于 ThreadLocal 的一些理解。今天是第二期,將著重分析 ThreadLocal 內(nèi)存泄露問題,文章后半篇含重點(diǎn)源碼精講,不容錯(cuò)過。廢話不多說,坐穩(wěn)發(fā)車咯!

上期回顧

在上一期,我通過閱讀源碼的方式帶大家學(xué)習(xí)了 ThreadLocal 常用的 API,并在這個(gè)過程中深度剖析了 ThreadLocal 的存儲(chǔ)結(jié)構(gòu)。

下面通過我剛剛繪制的一張圖來為大家回顧一下上一節(jié)所闡述的存儲(chǔ)結(jié)構(gòu)。

圖片圖片

如果大家對這個(gè)存儲(chǔ)結(jié)構(gòu)有所疑惑,可以回看第一期《ThreadLocal 源碼解讀:初識 ThreadLocal》。

引用類型

在 Java 中有四種常用的引用類型,依照引用的強(qiáng)弱排序依次是:強(qiáng)引用、軟引用、弱引用、幻引用(虛引用)。

其中強(qiáng)引用就是我們通常所說的引用,所以這里 Java 并沒有單獨(dú)定義一個(gè)引用類來表示,并且強(qiáng)引用存在時(shí)被引用對象一定不會(huì)被垃圾回收器回收。

軟引用在 Java 中使用 SoftReference 類表示,被軟引用單獨(dú)引用的對象當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候會(huì)被垃圾回收器所回收,也就是說在發(fā)生 OOM 前將會(huì)回收軟引用對象,試圖避免 OOM 的發(fā)生。

弱引用在 Java 中使用 WeakReference 類表示,被弱引用單獨(dú)引用的對象在發(fā)生任意垃圾回收時(shí),無論內(nèi)存是否充足都將會(huì)被回收。

幻引用在 Java 中使用 PhantomReference 類表示,是最弱的引用類型,主要用于跟蹤對象是否被垃圾回收,并且幻引用的 get 方法永遠(yuǎn)返回 null。

上述三種引用類均繼承 Reference 類,Reference 類通過泛型成員變量 referent 存儲(chǔ)引用對象,并提供了 get 方法用于獲取引用對象,提供 clear 方法用于清理引用對象。

圖片圖片

內(nèi)存泄露問題剖析

拋出觀點(diǎn)

在探究 ThreadLocal 內(nèi)存泄漏問題之前,我們首先要明確一下,什么是內(nèi)存泄露?

這里我們直接引用百度百科提供的答案。


內(nèi)存泄漏(Memory Leak)是指程序中已動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。

那么 ThreadLocal 在使用過程中存在泄露問題嗎?答案是肯定的,但是要糾正一點(diǎn),ThreadLocal 的內(nèi)存泄露問題與 ThreadLocal 對象的弱引用并無關(guān)系!這一點(diǎn)在網(wǎng)上可能存在著誤導(dǎo)信息,下面將會(huì)為大家論證我的觀點(diǎn)。

推理驗(yàn)證

首先我們來看一下 Entry 類的定義。

圖片圖片

可以看到 Entry 類繼承了 WeakReference 類,并且將弱引用的 ThreadLocal 對象作為了 ThreadLocalMap 的鍵。

查閱過 ThreadLocal 相關(guān)博客的小伙伴可能看過下面種說法。

--start--

ThreadLocal 變量如果未被正確清理,可能會(huì)導(dǎo)致內(nèi)存泄露。因?yàn)?ThreadLocalMap 的鍵是 ThreadLocal 對象的弱引用,值是強(qiáng)引用。

當(dāng) ThreadLocal 對象不再被外部引用時(shí),ThreadLocalMap 中的鍵會(huì)被垃圾回收,但值仍然存在,導(dǎo)致無法被垃圾回收,從而引發(fā)內(nèi)存泄露。

--end--

在這個(gè)過程中的確存在內(nèi)存泄露問題,但這和 ThreadLocalMap 的 key 設(shè)計(jì)并無關(guān)系,這是編寫程序的不嚴(yán)謹(jǐn)導(dǎo)致的問題,在使用完 ThreadLocal 后,沒有調(diào)用 remove 方法顯式移除值。

任何一個(gè) Java 對象都可能因?yàn)槭褂貌划?dāng)導(dǎo)致內(nèi)存泄漏,比如聲明了一個(gè)類的對象用作成員變量,但是卻從未在代碼里使用過這個(gè)成員變量(如下圖),這也是內(nèi)存泄漏。

圖片圖片

所以并不是因?yàn)?ThreadLocalMap 的 key 的弱引用設(shè)計(jì),才導(dǎo)致的內(nèi)存泄露問題。恰恰相反,ThreadLocalMap 的 key 的弱引用設(shè)計(jì)一定程度上減少了內(nèi)存泄露的損失。

首先當(dāng) ThreadLocalMap 的 key 不再被外部所引用時(shí),ThreadLocal 對象以及通過 ThreadLocal 存儲(chǔ)在 ThreadLocalMap 中的值已經(jīng)無法在其他地方被獲取,已經(jīng)發(fā)生了內(nèi)存泄漏。那么這時(shí)候垃圾回收器回收掉 ThreadLocalMap 的 key,恰恰為我們釋放了一部分已經(jīng)泄露的內(nèi)存。

這時(shí)候有人可能會(huì)有疑問,那 value 就不管了嗎?當(dāng)然不是!雖然這是開發(fā)者 API 使用不當(dāng)留下的坑,但是設(shè)計(jì)者也為我們填了這個(gè)坑。

注意看 Entry 類的注釋,這里我直接為大家翻譯出來。

圖片圖片

可以看到官方將 key 為 null 的 Entry 對象稱之為“陳舊條目”,也就是我上一期文章所說的過時(shí) Entry,并且官方指出這些過時(shí) Entry 可以從 ThreadLocalMap 中刪除。

那么不難猜到,ThreadLocal 在設(shè)計(jì)時(shí)一定在某些時(shí)機(jī)對這些過時(shí) Entry 進(jìn)行了清理,盡可能的釋放泄露的內(nèi)存。

這里先給出大家結(jié)論,然后我們再去論證:ThreadLocal在調(diào)用set(),get(),remove()方法的時(shí)候,都可能觸發(fā)清理過時(shí)Entry的邏輯。。

清理方法源碼剖析

expungeStaleEntry 方法

在討論到 ThreadLocalMap 過時(shí) Entry 清理的問題,就繞不開 ThreadLocalMap 的 expungeStaleEntry 這個(gè)方法,見名之意這個(gè)方法用于刪除過時(shí) Entry。

下面我將采用在源碼中添加注釋的方式剖析這個(gè)方法。

/**
 * Expunge a stale entry by rehashing any possibly colliding entries
 * lying between staleSlot and the next null slot.  This also expunges
 * any other stale entries encountered before the trailing null.  See
 * Knuth, Section 6.4
 *
 * @param staleSlot index of slot known to have null key
 * @return the index of the next null slot after staleSlot
 * (all between staleSlot and this slot will have been checked
 * for expunging).
 */
private int expungeStaleEntry(int staleSlot) {
    // 入?yún)?staleSlot: 待清理位置下標(biāo)
    
    // 獲取 ThreadLocalMap 中的 Entry 數(shù)組。
    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    // len 為當(dāng)前 Entry 數(shù)組容量。
    int len = tab.length;

    // expunge entry at staleSlot
    // 清除當(dāng)前 staleSlot 位置的過時(shí) Entry。
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    // 元素?cái)?shù)量減一。
    size--;

    // Rehash until we encounter null
    // 因?yàn)?ThreadLocalMap 解決哈希沖突采用的是線性探測法,如將當(dāng)前下標(biāo)位置賦值為 null ,但不對后續(xù) Entry
    // 元素進(jìn)行 rehash 操作,就可能導(dǎo)致存在哈希沖突的后置元素?zé)o法被探測到。所以將當(dāng)前元素清理后需要
    // 對后續(xù)元素進(jìn)行 rehash 操作,直到遇到下一個(gè)為 null 的元素。
    ThreadLocal.ThreadLocalMap.Entry e;
    int i;
    // nextIndex 用于向后遞增索引 ((i + 1 < len) ? i + 1 : 0)
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
            // 此時(shí) Entry 不為 null,key 為 null,Entry 為過時(shí) Entry 需清理掉。
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            // 此時(shí)為有效 Entry,需要進(jìn)行 rehash 操作重新定位。
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                // 進(jìn)入到這個(gè)分支說明 rehash 后,新的下標(biāo)與原來下標(biāo)不等。
                // 將當(dāng)前下標(biāo)位置清空。
                tab[i] = null;

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                // 從 h 位置開始遍歷,直到遇到為 null 的元素,并將 rehash 后的元素插入到該位置。
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    
    // i 為 staleSlot 后的第一個(gè) null 元素的位置下標(biāo)。
    return i;
}

了解了 expungeStaleEntry 方法的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)之后就可以把這個(gè)方法當(dāng)做一個(gè)黑盒,作用是清理傳入下標(biāo)位置的過時(shí) Entry,入?yún)橐粋€(gè)過時(shí) Entry 的下標(biāo)。

cleanSomeSlots 方法

有了 expungeStaleEntry 方法的基礎(chǔ)我們就可以攻克下一個(gè)和清理過時(shí) Entry 相關(guān)的方法:cleanSomeSlots。見名之意,這個(gè)方法的作用是清除一些過時(shí) Entry。

同樣采用在源碼中添加注釋的方式剖析這個(gè)方法。

/**
 * Heuristically scan some cells looking for stale entries.
 * This is invoked when either a new element is added, or
 * another stale one has been expunged. It performs a
 * logarithmic number of scans, as a balance between no
 * scanning (fast but retains garbage) and a number of scans
 * proportional to number of elements, that would find all
 * garbage but would cause some insertions to take O(n) time.
 *
 * @param i a position known NOT to hold a stale entry. The
 * scan starts at the element after i.
 *
 * @param n scan control: {@code log2(n)} cells are scanned,
 * unless a stale entry is found, in which case
 * {@code log2(table.length)-1} additional cells are scanned.
 * When called from insertions, this parameter is the number
 * of elements, but when from replaceStaleEntry, it is the
 * table length. (Note: all this could be changed to be either
 * more or less aggressive by weighting n instead of just
 * using straight log n. But this version is simple, fast, and
 * seems to work well.)
 *
 * @return true if any stale entries have been removed.
 */
private boolean cleanSomeSlots(int i, int n) {
    // 入?yún)?i: 一個(gè)已知不為過時(shí) Entry 的下標(biāo)。掃描從 i 之后的位置開始。
    // 入?yún)?n: 掃描次數(shù)控制值
    
    // 是否清理了任意過時(shí) Entry 標(biāo)志,
    // 為 false 代表本次方法調(diào)用未能清理任何過時(shí) Entry,為 true 代表本次方法調(diào)用至少清理了一個(gè)過時(shí) Entry。
    boolean removed = false;
    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    int len = tab.length;
    // doWhile 循環(huán),至少執(zhí)行一次。
    do {
        i = nextIndex(i, len);
        ThreadLocal.ThreadLocalMap.Entry e = tab[i];
        if (e != null && e.get() == null) {
            // 進(jìn)入到當(dāng)前分支說明當(dāng)前 Entry 為過時(shí) Entry。
            // 將 n 置為數(shù)組容量,將當(dāng)前循環(huán)遍歷次數(shù)進(jìn)行追增(n 變大了)。
            n = len;
            // 將標(biāo)志置為 true,證明本次方法調(diào)用并不是無功而返。
            removed = true;
            // 調(diào)用清理過時(shí) Entry 方法,并將 expungeStaleEntry 方法返回的 null 元素的下標(biāo)賦值給 i,
            // 在這之間的下標(biāo)都在 expungeStaleEntry 方法中進(jìn)行了清理,所以這里直接跳過避免重復(fù)操作。
            i = expungeStaleEntry(i);
        }
        // >>>= 無符號右移并賦值,相當(dāng)于除以 2 操作。
    } while ( (n >>>= 1) != 0);
    // 返回本次是否至少清理了一個(gè)過時(shí) Entry。
    return removed;
}

cleanSomeSlots 方法在 set 和 remove 方法調(diào)用中會(huì)被調(diào)用到,這個(gè)方法在完全不掃描以及全量掃描中做了一個(gè)平衡,采用以對數(shù)的方式進(jìn)行掃描,并且如果發(fā)現(xiàn)了過時(shí) Entry 則會(huì)再追增對數(shù)次掃描,使得在保證 set 方法和 remove 方法的執(zhí)行效率的情況下一定程度上清理了過時(shí) Entry。

replaceStaleEntry 方法

下面我們來看一下最后一個(gè)與清理過時(shí) key 有關(guān)的方法:replaceStaleEntry,通過方法名我們可以推測出這個(gè)方法的作用是替換過時(shí)條目,那么用什么替換呢,是 set 方法傳過來的 Entry。

同樣采用在源碼中添加注釋的方式剖析這個(gè)方法,這個(gè)方法有些許難度,如果大家不理解,可以多閱讀幾遍。

/**
 * Replace a stale entry encountered during a set operation
 * with an entry for the specified key.  The value passed in
 * the value parameter is stored in the entry, whether or not
 * an entry already exists for the specified key.
 *
 * As a side effect, this method expunges all stale entries in the
 * "run" containing the stale entry.  (A run is a sequence of entries
 * between two null slots.)
 *
 * @param  key the key
 * @param  value the value to be associated with key
 * @param  staleSlot index of the first stale entry encountered while
 *         searching for key.
 */
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    // 入?yún)?key: set 操作傳過來的 key
    // 入?yún)?value: set 操作傳過來的 value,與參數(shù) key 相關(guān)聯(lián)
    // 入?yún)?staleSlot: 待替換的過時(shí) Entry 的下標(biāo)
    
    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    int len = tab.length;
    ThreadLocal.ThreadLocalMap.Entry e;

    // Back up to check for prior stale entry in current run.
    // We clean out whole runs at a time to avoid continual
    // incremental rehashing due to garbage collector freeing
    // up refs in bunches (i.e., whenever the collector runs).
    // slotToExpunge 變量目標(biāo)是存儲(chǔ)當(dāng)前區(qū)間段(兩個(gè) null 元素之間),第一個(gè)過時(shí) Entry 的下標(biāo)。
    // 將入?yún)⒌倪^時(shí) Entry 下標(biāo)賦值給 slotToExpunge。
    int slotToExpunge = staleSlot;
    // 這里需要格外注意一下,這里并不是遞增下標(biāo),而是對下標(biāo)進(jìn)行遞減。
    // prevIndex ((i - 1 >= 0) ? i - 1 : len - 1)。
    // 向前進(jìn)行遍歷直到遇到為 null 的元素。
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        if (e.get() == null)
            // 將遍歷過程中過時(shí) Entry 的下標(biāo)賦值給 slotToExpunge 變量。
            // 經(jīng)過當(dāng)前遍歷邏輯,slotToExpunge 將存儲(chǔ)兩個(gè) null 元素之間第一個(gè)過時(shí) key 的下標(biāo)。
            slotToExpunge = i;

    // Find either the key or trailing null slot of run, whichever
    // occurs first
    // 這里是由入?yún)⒌倪^時(shí) Entry 下標(biāo)開始向后遍歷,直到遇到 null 元素。
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();

        // If we find key, then we need to swap it
        // with the stale entry to maintain hash table order.
        // The newly stale slot, or any other stale slot
        // encountered above it, can then be sent to expungeStaleEntry
        // to remove or rehash all of the other entries in run.
        if (k == key) {
            // 進(jìn)入當(dāng)前分支表示在遍歷的過程中找到了被 set 的 Entry 對象的本體。
            // 將和 key 關(guān)聯(lián)的新 value 值賦值給本體。
            e.value = value;
            // 操作一
            // 將 Entry 對象本體和入?yún)?staleSlot 位置的過時(shí) Entry 進(jìn)行交換,
            // 結(jié)果是set操作的 key 與 value,無論之前本體存儲(chǔ)在哪里,
            // 最終都會(huì)存儲(chǔ)在入?yún)⒌?staleSlot 下標(biāo),符合方法名中的 replace 含義。
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            // Start expunge at preceding stale entry if it exists
            if (slotToExpunge == staleSlot)
                // 如果當(dāng)前區(qū)間段第一個(gè)過時(shí) Entry 下標(biāo)仍是 staleSlot 下標(biāo),
                // 那么需要將當(dāng)前 i 下標(biāo)賦值給 slotToExpunge ,因?yàn)?staleSlot 下標(biāo)已經(jīng)存儲(chǔ)了 set 操作的 Entry 對象,
                // 導(dǎo)致當(dāng)前 i 下標(biāo)變成了第一個(gè)過時(shí) Entry 的下標(biāo)。
                slotToExpunge = i;
            // 先調(diào)用 expungeStaleEntry 方法清除 slotToExpunge 下標(biāo)的過時(shí) Entry,
            // 再從 expungeStaleEntry 方法返回的 null 元素的下標(biāo)開始執(zhí)行 cleanSomeSlots 方法。
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            // 已經(jīng)完成替換過時(shí)條目操作,退出當(dāng)前方法。
            return;
        }

        // If we didn't find stale entry on backward scan, the
        // first stale entry seen while scanning for key is the
        // first still present in the run.
        if (k == null && slotToExpunge == staleSlot)
            // 進(jìn)入當(dāng)前分支說明當(dāng)前 Entry 是過時(shí) Entry。
            // 如果當(dāng)前區(qū)間段第一個(gè)過時(shí) Entry 下標(biāo)仍是入?yún)⒌?staleSlot 下標(biāo),
            // 則需要將當(dāng)前位置下標(biāo)賦值給 slotToExpunge,因?yàn)樽罱K當(dāng)前位置的過時(shí) Entry 將是
            // 當(dāng)前區(qū)間段的第一個(gè)過時(shí) Entry。因?yàn)?staleSlot 下標(biāo)位置的過時(shí) Entry 在之后的邏輯
            // 里要么被交換到當(dāng)前下標(biāo)之后(上文操作一),要么被新的 set 傳入的 Entry 覆蓋掉(下文操作二)。
            slotToExpunge = i;
    }

    // If key not found, put new entry in stale slot
    // 操作二
    // 代碼執(zhí)行到當(dāng)前位置說明 set 的 key 與 value 是一個(gè)新的 Entry,在之前并不存在。
    // 以 set 方法傳入的 key 和 value 值 new 一個(gè)新的 Entry 對象,并覆蓋在入?yún)⒌?staleSlot 下標(biāo)處。
    tab[staleSlot].value = null;
    tab[staleSlot] = new ThreadLocal.ThreadLocalMap.Entry(key, value);

    // If there are any other stale entries in run, expunge them
    if (slotToExpunge != staleSlot)
        // slotToExpunge 與 staleSlot 相等則說明當(dāng)前區(qū)間段只有入?yún)?staleSlot 位置有過時(shí) Entry,
        // 并且該過時(shí) Entry 已被覆蓋,所以無需清理,無需進(jìn)入當(dāng)前分支。
        
        // 進(jìn)入當(dāng)前分支說明當(dāng)前區(qū)間段,除了被覆蓋的過時(shí) Entry,至少還存在一個(gè)過時(shí) Entry,
        // slotToExpunge 下標(biāo)為第一個(gè)過時(shí) Entry 的下標(biāo)。
        // 先調(diào)用 expungeStaleEntry 方法清除 slotToExpunge 下標(biāo)的過時(shí) Entry,
        // 再從 expungeStaleEntry 方法返回的 null 元素的下標(biāo)開始執(zhí)行 cleanSomeSlots 方法。
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

在方法中 slotToExpunge 變量之所以始終要存儲(chǔ)當(dāng)前區(qū)間段(兩個(gè) null 元素之間)的第一個(gè)過時(shí) Entry,是因?yàn)槊慨?dāng)刪除一個(gè)過時(shí) Entry 后都會(huì)對后續(xù) Entry 進(jìn)行 rehash 操作,如果清理的不是第一個(gè)過時(shí) Entry,那么在后續(xù)其他邏輯觸發(fā)清理第一個(gè)過時(shí) Entry 時(shí)還會(huì)將剛剛 rehash 過的元素再次 rehash 一遍,極大的影響效率。

至此在 ThreadLocalMap 中涉及清理過時(shí)Entry的三個(gè)方法都已剖析完畢,下面我們來羅列一下什么時(shí)候會(huì)觸發(fā)這三個(gè)方法。

清理方法調(diào)用梳理

為避免截圖過多影響閱讀體驗(yàn),這里將只粘出調(diào)用的起點(diǎn),并給調(diào)用鏈路,大家后續(xù)可以自己在源碼中點(diǎn)一點(diǎn)。

get方法

調(diào)用鏈路:ThreadLocal#get->ThreadLocalMap#getEntry->ThreadLocalMap#getEntryAfterMiss->ThreadLocalMap#expungeStaleEntry

圖片圖片

set方法

調(diào)用鏈路 1:ThreadLocal#set->ThreadLocalMap#set->ThreadLocalMap#replaceStaleEntry

調(diào)用鏈路 2:ThreadLocal#set->ThreadLocalMap#set->ThreadLocalMap#cleanSomeSlots

調(diào)用鏈路 3:ThreadLocal#set->ThreadLocalMap#set->ThreadLocalMap#rehash->ThreadLocalMap#expungeStaleEntries->ThreadLocalMap#expungeStaleEntry

圖片圖片

remove方法

調(diào)用鏈路:ThreadLocal#remove->ThreadLocalMap#remove->ThreadLocalMap#expungeStaleEntry

圖片圖片

總結(jié)

通過兩期文章的深度剖析,大家應(yīng)該對 ThreadLocal 的 API 使用以及內(nèi)存泄露問題有了進(jìn)一步的理解。

ThreadLocal 優(yōu)勢是無鎖化提升并發(fā)性能和簡化變量的傳遞邏輯。

在實(shí)際業(yè)務(wù)中使用 ThreadLocal 類時(shí)應(yīng)該在恰當(dāng)位置調(diào)用 remove 方法顯式移除值。

盡可能的避免觸發(fā) ThreadLocal 清理過時(shí) Entry 的邏輯,從而提高 ThreadLocal 性能。

例如使用繼承的 ThreadLocal 類,并重寫 finalize 方法,確保 ThreadLocal 對象在被垃圾回收前,remove 方法會(huì)被調(diào)用。

責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2022-08-26 07:33:49

內(nèi)存JVMEntry

2021-04-23 20:59:02

ThreadLocal內(nèi)存

2024-10-28 08:15:32

2018-10-25 15:24:10

ThreadLocal內(nèi)存泄漏Java

2022-10-18 08:38:16

內(nèi)存泄漏線程

2017-01-11 14:02:32

JVM源碼內(nèi)存

2024-06-24 08:11:37

2023-11-03 08:10:49

ThreadLoca內(nèi)存泄露

2023-05-29 07:17:48

內(nèi)存溢出場景

2013-12-23 09:25:21

2023-09-22 17:34:37

內(nèi)存remove方法

2021-05-26 08:02:03

ThreadLocal多線程多線程并發(fā)安全

2021-05-10 11:55:57

ThreadLocal內(nèi)存Java

2024-03-22 13:31:00

線程策略線程池

2010-10-25 10:10:27

ibmdwJava

2017-01-12 14:52:03

JVMFinalRefere源碼

2011-08-16 09:34:34

Nginx

2020-06-23 09:48:09

Python開發(fā)內(nèi)存

2010-05-31 16:53:21

Java

2024-11-18 16:15:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 免费精品视频在线观看 | 一级毛片成人免费看a | 毛片免费观看 | 蜜桃官网 | 91porn成人精品 | 狠狠热视频 | 欧美黑人激情 | 色婷婷久久久久swag精品 | 亚洲精品一区在线 | 中文字幕一区二区三区乱码在线 | 盗摄精品av一区二区三区 | www.黄色网| 最新中文字幕在线 | 成人免费视频一区二区 | 福利网址 | 精品日韩一区 | 国产成人短视频在线观看 | 亚洲免费人成在线视频观看 | 精品国产青草久久久久福利 | 中文字幕精品一区久久久久 | 亚洲成人免费 | 亚洲第一免费播放区 | 亚洲精品一区二区在线观看 | 久久99这里只有精品 | 日韩av免费看 | 国产精品国产a | 久久久精品综合 | 久久青| 精品一区av | 日韩欧美在线播放 | 91丨九色丨国产在线 | 国产成人精品一区二 | 日韩一区二区三区视频 | 日韩国产精品一区二区三区 | 欧美一区二区三区在线观看 | 欧美精品成人一区二区三区四区 | 久久成人免费 | 中文字幕免费在线观看 | 天天av网| 国产福利91精品 | 午夜欧美一区二区三区在线播放 |