自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

[[341667]]

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

因為人家明明也考慮到了很多情況,做了很多事情,保證了如果沒有 remove ,也有對 key 值為 null 時進行回收的處理操作

啥?你竟然不信?我 ThreadLocal 難道會騙你么

今天為了證明一下自己,我打算從組成的源碼開始講起,在 get , set 方法中都有對 key 值為 null 時進行回收的處理操作,先來看 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 不為空,說明 hash 沖突,需要向后查找 
  14.         e != null
  15.         // 從這里可以看出, ThreadLocalMap 采用的是開放地址法解決的 hash 沖突 
  16.         // 是最經(jīng)典的 線性探測法 --> 我覺得之所以選擇這種方法解決沖突時因為數(shù)據(jù)量不大 
  17.         e = tab[i = nextIndex(i, len)]) { 
  18.         ThreadLocal<?> k = e.get(); 
  19.  
  20.         // 要查找的 ThreadLocal 對象找到了,直接設置需要設置的值,然后 return 
  21.         if (k == key) { 
  22.             e.value = value; 
  23.             return
  24.         } 
  25.  
  26.         // 如果 k 為 null ,說明有 value 沒有及時回收,此時通過 replaceStaleEntry 進行處理 
  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 值沒有及時回收 
  38.     // 如果 sz >= threshold ,則需要擴容,重新 hash 即, rehash(); 
  39.     if (!cleanSomeSlots(i, sz) && sz >= threshold) 
  40.         rehash(); 

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

接下來瞅瞅 replaceStaleEntry 都干了點兒啥

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.     // 從當前 staleSlot 位置開始向前遍歷 
  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.             // 當 e.get() == null 時, slotToExpunge 記錄下此時的 i 值 
  14.             // 即 slotToExpunge 記錄的是 staleSlot 左手邊第一個空的 Entry 
  15.             slotToExpunge = i; 
  16.  
  17.     // 接下來從當前 staleSlot 位置向后遍歷 
  18.     // 這兩個遍歷是為了清理在左邊遇到的第一個空的 entry 到右邊的第一個空的 entry 之間所有過期的對象 
  19.     // 但是如果在向后遍歷過程中,找到了需要設置值的 key ,就開始清理,不會再繼續(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 說明在插入之前就已經(jīng)有相同的 key 值存在,所以需要替換舊的值 
  26.         // 同時和前面過期的對象進行交換位置 
  27.         if (k == key) { 
  28.             e.value = value; 
  29.  
  30.             tab[i] = tab[staleSlot]; 
  31.             tab[staleSlot] = e; 
  32.  
  33.             // 如果 slotToExpunge == staleSlot 說明向前遍歷時沒有找到過期的 
  34.             if (slotToExpunge == staleSlot) 
  35.                 slotToExpunge = i; 
  36.             // 進行清理過期數(shù)據(jù) 
  37.             cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 
  38.             return
  39.         } 
  40.  
  41.         // 如果在向后遍歷時,沒有找到 value 被回收的 Entry 對象 
  42.         // 且剛開始 staleSlot 的 key 為空,那么它本身就是需要設置 value 的 Entry 對象 
  43.         // 此時不涉及到清理 
  44.         if (k == null && slotToExpunge == staleSlot) 
  45.             slotToExpunge = i; 
  46.     } 
  47.  
  48.     // 如果 key 在數(shù)組中找不到,那就好說了,直接創(chuàng)建一個新的就可以了 
  49.     tab[staleSlot].value = null
  50.     tab[staleSlot] = new Entry(key, value); 
  51.  
  52.     // 如果 slotToExpunge != staleSlot 說明存在過期的對象,就需要進行清理 
  53.     if (slotToExpunge != staleSlot) 
  54.         cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 

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

  1. if (e.get() == null
  2.     // 當 e.get() == null 時, slotToExpunge 記錄下此時的 i 值 
  3.     // 即 slotToExpunge 記錄的是 staleSlot 左手邊第一個空的 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 說明向前遍歷時沒有找到過期的 
  13.     if (slotToExpunge == staleSlot) 
  14.         slotToExpunge = i; 
  15.         // 進行清理過期數(shù)據(jù) 
  16.         cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 
  17.         return

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

看第一個 if ,當 e.get() == null 時,此時將 i 的值給 slotToExpunge

第二個 if ,當 k ==key 時,此時將 i 給了 staleSlot 來進行交換

為什么要對 staleSlot 進行交換呢?畫圖說明一下

如下圖,假設此時表長為 10 ,其中下標為 3 和 5 的 key 已經(jīng)被回收( key 被回收掉的就是 null ),因為采用的開放地址法,所以 15 mod 10 應該是 5 ,但是因為位置被占,所以在 6 的位置,同樣 25 mod 10 也應該是 5 ,但是因為位置被占,下個位置也被占,所以就在第 7 號的位置上了

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

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

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

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

在上面代碼中有一行代碼 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); ,說明接下來的處理是交給了 expungeStaleEntry ,接下來去分析一下 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 ,說明 value 就應該被回收掉 
  18.         if (k == null) { 
  19.             // 此時直接將 e.value 置為 null  
  20.             // 這樣就將 thread -> threadLocalMap -> value 這條引用鏈給打破 
  21.             // 方便了 GC 
  22.             e.value = null
  23.             tab[i] = null
  24.             size--; 
  25.         } else { 
  26.             // 這個時候要重新 hash ,因為采用的是開放地址法,所以可以理解為就是將后面的元素向前移動 
  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; 

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

此時傳進來的 staleSlot 值為 6 ,因為此時的 key 為 null ,所以接下來會走 e.value = null ,這一步結(jié)束之后,就成了:

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

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

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

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

分析到這里應該就比較明確了,在 expungeStaleEntry 中,有些地方是幫助 GC 的,而通過源碼能夠發(fā)現(xiàn), set 方法調(diào)用了該方法進行了 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.     // 一直探測尋找下一個元素,直到找到的元素是要找的 
  16.     while (e != null) { 
  17.         ThreadLocal<?> k = e.get(); 
  18.         if (k == key
  19.             return e; 
  20.         if (k == null
  21.             // 如果 k == null 說明有 value 沒有及時回收 
  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 方法都有進行幫助 GC ,所以正常情況下是不會有內(nèi)存溢出的,但是如果創(chuàng)建了之后一直沒有調(diào)用 get 或者 set 方法,還是有可能會內(nèi)存溢出

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

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

 

責任編輯:武曉燕 來源: Java極客技術
相關推薦

2020-09-10 07:40:28

ThreadLocal內(nèi)存

2021-02-18 16:53:44

內(nèi)存ThreadLocal線程

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-03-03 13:54:31

TypeScript編譯器Chirag

2021-06-28 10:06:21

開源文本識別pyWhat

2020-07-08 07:44:35

面試阿里加班

2019-08-12 10:27:34

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

2013-10-31 15:52:11

2015-05-06 10:11:48

2014-01-17 14:39:18

12306 搶票

2021-02-05 07:33:44

攻略面試項目

2009-04-28 10:05:52

阿爾卡特朗韋華恩華為

2021-04-21 07:53:12

Java限流器管理

2020-03-09 10:21:12

Java集合類 Guava

2018-03-22 14:59:20

2017-09-07 16:52:23

2022-03-28 15:15:15

神經(jīng)網(wǎng)絡編程開發(fā)

2013-12-18 16:20:20

雷軍小米
點贊
收藏

51CTO技術棧公眾號