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

阿粉昨天說(shuō)我動(dòng)不動(dòng)就內(nèi)存泄漏,我好委屈...

存儲(chǔ) 存儲(chǔ)軟件
大家好,我是 ThreadLocal ,昨天阿粉說(shuō)我動(dòng)不動(dòng)就內(nèi)存泄漏,我蠻委屈的,我才沒(méi)有冤枉他嘞,證據(jù)在這里: ThreadLocal 你怎么動(dòng)不動(dòng)就內(nèi)存泄漏?

[[341667]]

 大家好,我是 ThreadLocal ,昨天阿粉說(shuō)我動(dòng)不動(dòng)就內(nèi)存泄漏,我蠻委屈的,我才沒(méi)有冤枉他嘞,證據(jù)在這里: ThreadLocal 你怎么動(dòng)不動(dòng)就內(nèi)存泄漏?

因?yàn)槿思颐髅饕部紤]到了很多情況,做了很多事情,保證了如果沒(méi)有 remove ,也有對(duì) key 值為 null 時(shí)進(jìn)行回收的處理操作

啥?你竟然不信?我 ThreadLocal 難道會(huì)騙你么

今天為了證明一下自己,我打算從組成的源碼開(kāi)始講起,在 get , set 方法中都有對(duì) key 值為 null 時(shí)進(jìn)行回收的處理操作,先來(lái)看 set 方法是怎么做的

set

下面是 set 方法的源碼:

  1. private void set(ThreadLocal<?> key, Object value) { 
  2.  
  3.     // We don't use a fast path as with get() because it is at 
  4.     // least as common to use set() to create new entries as 
  5.     // it is to replace existing ones, in which case, a fast 
  6.     // path would fail more often than not
  7.  
  8.     Entry[] tab = table
  9.     int len = tab.length; 
  10.     int i = key.threadLocalHashCode & (len-1); 
  11.  
  12.     for (Entry e = tab[i]; 
  13.         // 如果 e 不為空,說(shuō)明 hash 沖突,需要向后查找 
  14.         e != null
  15.         // 從這里可以看出, ThreadLocalMap 采用的是開(kāi)放地址法解決的 hash 沖突 
  16.         // 是最經(jīng)典的 線(xiàn)性探測(cè)法 --> 我覺(jué)得之所以選擇這種方法解決沖突時(shí)因?yàn)閿?shù)據(jù)量不大 
  17.         e = tab[i = nextIndex(i, len)]) { 
  18.         ThreadLocal<?> k = e.get(); 
  19.  
  20.         // 要查找的 ThreadLocal 對(duì)象找到了,直接設(shè)置需要設(shè)置的值,然后 return 
  21.         if (k == key) { 
  22.             e.value = value; 
  23.             return
  24.         } 
  25.  
  26.         // 如果 k 為 null ,說(shuō)明有 value 沒(méi)有及時(shí)回收,此時(shí)通過(guò) replaceStaleEntry 進(jìn)行處理 
  27.         // replaceStaleEntry 具體內(nèi)容等下分析 
  28.         if (k == null) { 
  29.             replaceStaleEntry(key, value, i); 
  30.             return
  31.         } 
  32.     } 
  33.  
  34.     // 如果 tab[i] == null ,則直接創(chuàng)建新的 entry 即可 
  35.     tab[i] = new Entry(key, value); 
  36.     int sz = ++size
  37.     // 在創(chuàng)建之后調(diào)用 cleanSomeSlots 方法檢查是否有 value 值沒(méi)有及時(shí)回收 
  38.     // 如果 sz >= threshold ,則需要擴(kuò)容,重新 hash 即, rehash(); 
  39.     if (!cleanSomeSlots(i, sz) && sz >= threshold) 
  40.         rehash(); 

通過(guò)源碼可以看到,在 set 方法中,主要是通過(guò) replaceStaleEntry 方法和 cleanSomeSlots 方法去做的檢測(cè)和處理

接下來(lái)瞅瞅 replaceStaleEntry 都干了點(diǎn)兒啥

replaceStaleEntry

  1. private void replaceStaleEntry(ThreadLocal<?> key, Object value, 
  2.                                 int staleSlot) { 
  3.     Entry[] tab = table
  4.     int len = tab.length; 
  5.     Entry e; 
  6.  
  7.     // 從當(dāng)前 staleSlot 位置開(kāi)始向前遍歷 
  8.     int slotToExpunge = staleSlot; 
  9.     for (int i = prevIndex(staleSlot, len); 
  10.         (e = tab[i]) != null
  11.         i = prevIndex(i, len)) 
  12.         if (e.get() == null
  13.             // 當(dāng) e.get() == null 時(shí), slotToExpunge 記錄下此時(shí)的 i 值 
  14.             // 即 slotToExpunge 記錄的是 staleSlot 左手邊第一個(gè)空的 Entry 
  15.             slotToExpunge = i; 
  16.  
  17.     // 接下來(lái)從當(dāng)前 staleSlot 位置向后遍歷 
  18.     // 這兩個(gè)遍歷是為了清理在左邊遇到的第一個(gè)空的 entry 到右邊的第一個(gè)空的 entry 之間所有過(guò)期的對(duì)象 
  19.     // 但是如果在向后遍歷過(guò)程中,找到了需要設(shè)置值的 key ,就開(kāi)始清理,不會(huì)再繼續(xù)向下遍歷 
  20.     for (int i = nextIndex(staleSlot, len); 
  21.         (e = tab[i]) != null
  22.         i = nextIndex(i, len)) { 
  23.         ThreadLocal<?> k = e.get(); 
  24.  
  25.         // 如果 k == key 說(shuō)明在插入之前就已經(jīng)有相同的 key 值存在,所以需要替換舊的值 
  26.         // 同時(shí)和前面過(guò)期的對(duì)象進(jìn)行交換位置 
  27.         if (k == key) { 
  28.             e.value = value; 
  29.  
  30.             tab[i] = tab[staleSlot]; 
  31.             tab[staleSlot] = e; 
  32.  
  33.             // 如果 slotToExpunge == staleSlot 說(shuō)明向前遍歷時(shí)沒(méi)有找到過(guò)期的 
  34.             if (slotToExpunge == staleSlot) 
  35.                 slotToExpunge = i; 
  36.             // 進(jìn)行清理過(guò)期數(shù)據(jù) 
  37.             cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 
  38.             return
  39.         } 
  40.  
  41.         // 如果在向后遍歷時(shí),沒(méi)有找到 value 被回收的 Entry 對(duì)象 
  42.         // 且剛開(kāi)始 staleSlot 的 key 為空,那么它本身就是需要設(shè)置 value 的 Entry 對(duì)象 
  43.         // 此時(shí)不涉及到清理 
  44.         if (k == null && slotToExpunge == staleSlot) 
  45.             slotToExpunge = i; 
  46.     } 
  47.  
  48.     // 如果 key 在數(shù)組中找不到,那就好說(shuō)了,直接創(chuàng)建一個(gè)新的就可以了 
  49.     tab[staleSlot].value = null
  50.     tab[staleSlot] = new Entry(key, value); 
  51.  
  52.     // 如果 slotToExpunge != staleSlot 說(shuō)明存在過(guò)期的對(duì)象,就需要進(jìn)行清理 
  53.     if (slotToExpunge != staleSlot) 
  54.         cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 

在 replaceStaleEntry 方法中,需要注意一下剛開(kāi)始的兩個(gè) for 循環(huán)中內(nèi)容(在這里再貼一下):

  1. if (e.get() == null
  2.     // 當(dāng) e.get() == null 時(shí), slotToExpunge 記錄下此時(shí)的 i 值 
  3.     // 即 slotToExpunge 記錄的是 staleSlot 左手邊第一個(gè)空的 Entry 
  4.     slotToExpunge = i; 
  5.  
  6. if (k == key) { 
  7.     e.value = value; 
  8.  
  9.     tab[i] = tab[staleSlot]; 
  10.     tab[staleSlot] = e; 
  11.                                          
  12.     // 如果 slotToExpunge == staleSlot 說(shuō)明向前遍歷時(shí)沒(méi)有找到過(guò)期的 
  13.     if (slotToExpunge == staleSlot) 
  14.         slotToExpunge = i; 
  15.         // 進(jìn)行清理過(guò)期數(shù)據(jù) 
  16.         cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 
  17.         return

這兩個(gè) for 循環(huán)中的 if 到底是在做什么?

看第一個(gè) if ,當(dāng) e.get() == null 時(shí),此時(shí)將 i 的值給 slotToExpunge

第二個(gè) if ,當(dāng) k ==key 時(shí),此時(shí)將 i 給了 staleSlot 來(lái)進(jìn)行交換

為什么要對(duì) staleSlot 進(jìn)行交換呢?畫(huà)圖說(shuō)明一下

如下圖,假設(shè)此時(shí)表長(zhǎng)為 10 ,其中下標(biāo)為 3 和 5 的 key 已經(jīng)被回收( key 被回收掉的就是 null ),因?yàn)椴捎玫拈_(kāi)放地址法,所以 15 mod 10 應(yīng)該是 5 ,但是因?yàn)槲恢帽徽迹栽?6 的位置,同樣 25 mod 10 也應(yīng)該是 5 ,但是因?yàn)槲恢帽徽迹聜€(gè)位置也被占,所以就在第 7 號(hào)的位置上了

按照上面的分析,此時(shí) slotToExpunge 值為 3 , staleSlot 值為 5 , i 為 6

假設(shè),假設(shè)這個(gè)時(shí)候如果不進(jìn)行交換,而是直接回收的話(huà),此時(shí)位置為 5 的數(shù)據(jù)就被回收掉,然后接下來(lái)要插入一個(gè) key 為 15 的數(shù)據(jù),此時(shí) 15 mod 10 算出來(lái)是 5 ,正好這個(gè)時(shí)候位置為 5 的被回收完畢,這個(gè)位置就被空出來(lái)了,那么此時(shí)就會(huì)這樣:

同樣的 key 值竟然出現(xiàn)了兩次?!

這肯定是不希望看到的結(jié)果,所以一定要進(jìn)行數(shù)據(jù)交換

在上面代碼中有一行代碼 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); ,說(shuō)明接下來(lái)的處理是交給了 expungeStaleEntry ,接下來(lái)去分析一下 expungeStaleEntry

expungeStaleEntry

  1. private int expungeStaleEntry(int staleSlot) { 
  2.     Entry[] tab = table
  3.     int len = tab.length; 
  4.  
  5.     // expunge entry at staleSlot 
  6.     tab[staleSlot].value = null
  7.     tab[staleSlot] = null
  8.     size--; 
  9.  
  10.     // Rehash until we encounter null 
  11.     Entry e; 
  12.     int i; 
  13.     for (i = nextIndex(staleSlot, len); 
  14.         (e = tab[i]) != null
  15.         i = nextIndex(i, len)) { 
  16.         ThreadLocal<?> k = e.get(); 
  17.         // 如果 k == null ,說(shuō)明 value 就應(yīng)該被回收掉 
  18.         if (k == null) { 
  19.             // 此時(shí)直接將 e.value 置為 null  
  20.             // 這樣就將 thread -> threadLocalMap -> value 這條引用鏈給打破 
  21.             // 方便了 GC 
  22.             e.value = null
  23.             tab[i] = null
  24.             size--; 
  25.         } else { 
  26.             // 這個(gè)時(shí)候要重新 hash ,因?yàn)椴捎玫氖情_(kāi)放地址法,所以可以理解為就是將后面的元素向前移動(dòng) 
  27.             int h = k.threadLocalHashCode & (len - 1); 
  28.             if (h != i) { 
  29.                 tab[i] = null
  30.  
  31.                 // Unlike Knuth 6.4 Algorithm R, we must scan until 
  32.                 // null because multiple entries could have been stale. 
  33.                 while (tab[h] != null
  34.                     h = nextIndex(h, len); 
  35.                 tab[h] = e; 
  36.             } 
  37.         } 
  38.     } 
  39.     return i; 

因?yàn)槭窃?replaceStaleEntry 方法中調(diào)用的此方法,傳進(jìn)來(lái)的值是 staleSlot ,繼續(xù)上圖,經(jīng)過(guò) replaceStaleEntry 之后,它的數(shù)據(jù)結(jié)構(gòu)是這樣:

此時(shí)傳進(jìn)來(lái)的 staleSlot 值為 6 ,因?yàn)榇藭r(shí)的 key 為 null ,所以接下來(lái)會(huì)走 e.value = null ,這一步結(jié)束之后,就成了:

接下來(lái) i 為 7 ,此時(shí)的 key 不為 null ,那么就會(huì)重新 hash : int h = k.threadLocalHashCode & (len - 1); ,得到的 h 應(yīng)該是 5 ,但是實(shí)際上 i 為 7 ,說(shuō)明出現(xiàn)了 hash 沖突,就會(huì)繼續(xù)向下走,最終的結(jié)果是這樣:

可以看到,原來(lái)的 key 為 null ,值為 V5 的已經(jīng)被回收掉了。我認(rèn)為之所以回收掉之后,還要再次進(jìn)行重新 hash ,就是為了防止 key 值重復(fù)插入情況的發(fā)生

假設(shè) key 為 25 的并沒(méi)有進(jìn)行向前移動(dòng),也就是它還在位置 7 ,位置 6 是空的,再插入一個(gè) key 為 25 ,經(jīng)過(guò) hash 應(yīng)該在位置 5 ,但是有數(shù)據(jù)了,那就向下走,到了位置 6 ,誒,竟然是空的,趕緊插進(jìn)去,這不就又造成了上面說(shuō)到的問(wèn)題,同樣的一個(gè) key 竟然出現(xiàn)了兩次?!

而且經(jīng)過(guò) expungeStaleEntry 之后,將 key 為 null 的值,也設(shè)置為了 null ,這樣就方便 GC

分析到這里應(yīng)該就比較明確了,在 expungeStaleEntry 中,有些地方是幫助 GC 的,而通過(guò)源碼能夠發(fā)現(xiàn), set 方法調(diào)用了該方法進(jìn)行了 GC 處理, get 方法也有,不信你瞅瞅:

get

  1. private Entry getEntry(ThreadLocal<?> key) { 
  2.     int i = key.threadLocalHashCode & (table.length - 1); 
  3.     Entry e = table[i]; 
  4.     // 如果能夠找到尋找的值,直接 return 即可 
  5.     if (e != null && e.get() == key
  6.         return e; 
  7.     else 
  8.         // 如果找不到,則調(diào)用 getEntryAfterMiss 方法去處理 
  9.         return getEntryAfterMiss(key, i, e); 
  10.  
  11. private Entry getEntryAfterMiss(ThreadLocal<?> keyint i, Entry e) { 
  12.     Entry[] tab = table
  13.     int len = tab.length; 
  14.  
  15.     // 一直探測(cè)尋找下一個(gè)元素,直到找到的元素是要找的 
  16.     while (e != null) { 
  17.         ThreadLocal<?> k = e.get(); 
  18.         if (k == key
  19.             return e; 
  20.         if (k == null
  21.             // 如果 k == null 說(shuō)明有 value 沒(méi)有及時(shí)回收 
  22.             // 調(diào)用 expungeStaleEntry 方法去處理,幫助 GC 
  23.             expungeStaleEntry(i); 
  24.         else 
  25.             i = nextIndex(i, len); 
  26.         e = tab[i]; 
  27.     } 
  28.     return null

get 和 set 方法都有進(jìn)行幫助 GC ,所以正常情況下是不會(huì)有內(nèi)存溢出的,但是如果創(chuàng)建了之后一直沒(méi)有調(diào)用 get 或者 set 方法,還是有可能會(huì)內(nèi)存溢出

所以最保險(xiǎn)的方法就是,使用完之后就及時(shí) remove 一下,加快垃圾回收,就完美的避免了垃圾回收

我 ThreadLocal 雖然沒(méi)辦法做到 100% 的解決內(nèi)存泄漏問(wèn)題,但是我能做到 80% 不也應(yīng)該夸夸我嘛

 

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

2020-09-10 07:40:28

ThreadLocal內(nèi)存

2021-02-18 16:53:44

內(nèi)存ThreadLocal線(xiàn)程

2020-07-02 09:15:59

Netty內(nèi)存RPC

2020-11-09 06:00:04

Windows 10Windows操作系統(tǒng)

2023-02-28 07:34:39

ORAOracleMAP

2021-06-28 10:06:21

開(kāi)源文本識(shí)別pyWhat

2021-03-03 13:54:31

TypeScript編譯器Chirag

2019-08-12 10:27:34

前端程序員網(wǎng)絡(luò)

2013-10-31 15:52:11

2015-05-06 10:11:48

2020-07-08 07:44:35

面試阿里加班

2014-01-17 14:39:18

12306 搶票

2021-02-05 07:33:44

攻略面試項(xiàng)目

2021-04-21 07:53:12

Java限流器管理

2009-04-28 10:05:52

阿爾卡特朗韋華恩華為

2020-03-09 10:21:12

Java集合類(lèi) Guava

2018-03-22 14:59:20

2017-09-07 16:52:23

2021-04-29 07:15:20

動(dòng)態(tài)規(guī)劃DP

2022-03-28 15:15:15

神經(jīng)網(wǎng)絡(luò)編程開(kāi)發(fā)
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 国产精品日韩欧美一区二区三区 | 亚洲综合在线视频 | 国产黄色在线观看 | 久久国内精品 | 亚洲精品成人 | 亚洲精品中文字幕在线观看 | 在线观看视频一区二区三区 | 成人免费视频一区 | 欧美男人天堂 | 欧美一级视频免费看 | 不卡一区二区三区四区 | 三级成人在线观看 | 伦理一区二区 | 国产在线永久免费 | 亚洲精品视频免费看 | 成人免费淫片aa视频免费 | 亚洲美女网站 | 欧美一区二区三区久久精品 | 一级在线毛片 | 色888www视频在线观看 | 国产中文区二幕区2012 | 亚洲欧美一区二区三区1000 | 日韩精品在线免费观看 | 韩国久久 | 成人性视频免费网站 | 日韩精品视频在线 | 欧美精品成人一区二区三区四区 | 国产日韩av一区二区 | 亚洲免费精品 | 国产1区 | 亚洲欧洲成人av每日更新 | 成人午夜激情 | 成人性视频免费网站 | 丁香综合 | 日韩中文一区二区三区 | 国产精品视频中文字幕 | 国产精品不卡视频 | 久久久亚洲综合 | 在线视频91 | 99久久久久久久 | 免费久久精品 |