ThreadLocal原始碼解讀和記憶體洩露分析
什麼是TheadLocal
在多執行緒環境下,每個執行緒可以將自己的私有值儲存到ThreadLocal,使用時從ThreadLocal中取出,起到一個資料隔離,保證執行緒安全的作用。
public
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
每個執行緒都有一個關聯的ThreadLocalMap,ThreadLocalMap中儲存了多個ThreadLocal-value的鍵值組。
ThreadLocalMap原始碼
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
ThreadLocalMap內部維護一個弱引用的Entry[]陣列,存放ThreadLocal - value 的鍵值對組。
ThreadLocal的get方法
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();
}
獲取執行緒繫結的ThreadLocalMap,呼叫getEntry方法獲取value,如果map為空,重新繫結thread和threadLocalThreadLocalMap的getEntry方法
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);
}
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;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
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;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
1.根據TheadLocal的hash值,計算出在陣列中下表,找到key相同的entry
2. 否則,向下遍歷找下一個,直到找到ThreadLocal相同的entry
3. 遍歷過程中,如果key為null,將當前的entry回收,並且重新處理Entry陣列,將key為null的Entry清除。
ThreadLocal的set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
獲取執行緒繫結的ThreadLocalMap,呼叫TheadLocalMap的set方法ThreadLocalMap的set方法
private void set(ThreadLocal key, Object value) {
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)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);//當前索引位置,設定新的entry,並清除過期的entry
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//清除過期的entry,並按照原來的兩倍大小擴容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
1. 根據key的hash值,確定到Entry陣列的下標,如果下標在陣列中,並且key相同,就替換
2. 如果當前下標的entry為空,就會新設定一個entry到這個索引位置,並會清除過期的entry
3. 如果下標不在當前陣列中,就新增entry,並清除過期的entry,如沒有過期的entry並且size>=threshold,就會按照兩倍length大小擴容。
ThreadLocal的remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
獲取執行緒繫結的ThreadLocalMap,呼叫remove方法
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;
}
}
}
public void clear() {
this.referent = null;
}
找到key相同的entry,將引用置null,使得gc回收這個entry,並會清除過期的entry.
注意,ThreadLocal的get/set/remove方法都去清除過期的entry
ThreadLocal導致的記憶體洩露分析
棧中執行緒引用通過強引用指向堆中的Thread物件和ThreadLocalMap(value實際存放在ThreadLocalMap裡面的Entry)
Entry類的原始碼:
獲取到的引用是弱引用,也就是當ThreadLocal被GC回收的時候,entry可能沒有被GC,這就造成了記憶體洩露
那entry什麼時候被回收呢?
只有當current thread終止時候,ThreadLocalMap被gc了,這個entry才會被回收。
如何解決
ThreadLocal的get/set/remove方法都會將過期的entry刪除,只要呼叫其中一個方法就行。
一般的解決方法是,在get到值後,呼叫ThreadLocal的remove方法,將entry主動清除。
相關文章
- ThreadLocal原始碼解析,記憶體洩露以及傳遞性thread原始碼記憶體洩露
- Lowmemorykiller記憶體洩露分析記憶體洩露
- Linux記憶體洩露案例分析和記憶體管理分享Linux記憶體洩露
- ThreadLocal原理用法詳解ThreadLocal記憶體洩漏thread記憶體
- 使用 mtrace 分析 “記憶體洩露”記憶體洩露
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- Android 記憶體洩露詳解Android記憶體洩露
- Spring Boot heapdump洩露記憶體分析方法Spring Boot記憶體
- SHBrowseForFolder 記憶體洩露記憶體洩露
- Java面試題:細數ThreadLocal大坑,記憶體洩露本可避免Java面試題thread記憶體洩露
- ThreadLocal記憶體洩漏問題thread記憶體
- ThreadLocal真會記憶體洩漏?thread記憶體
- 一篇文章,從原始碼深入詳解ThreadLocal記憶體洩漏問題原始碼thread記憶體
- 解決git記憶體洩露問題Git記憶體洩露
- 一個 Vue 頁面的記憶體洩露分析Vue記憶體洩露
- 一個Vue頁面的記憶體洩露分析Vue記憶體洩露
- 小題大做 | Handler記憶體洩露全面分析記憶體洩露
- 面試:為了進阿里,死磕了ThreadLocal記憶體洩露原因面試阿里thread記憶體洩露
- 分析ThreadLocal的弱引用與記憶體洩漏問題thread記憶體
- JVM原始碼分析之堆外記憶體完全解讀JVM原始碼記憶體
- ThreadLocal原始碼解讀thread原始碼
- ThreadLocal 原始碼解讀thread原始碼
- ThreadLocal記憶體洩漏怎麼回事thread記憶體
- 分析記憶體洩漏和goroutine洩漏記憶體Go
- JAVA記憶體洩露的原因及解決Java記憶體洩露
- 利用dotnet-dump分析docker容器記憶體洩露Docker記憶體洩露
- 簡單的記憶體“洩露”和“溢位”記憶體
- 記一次 .NET 某工控軟體 記憶體洩露分析記憶體洩露
- 實戰Go記憶體洩露Go記憶體洩露
- 使用Windbg快速分析應用記憶體洩露問題記憶體洩露
- 經驗之談:記憶體洩露的原因以及分析記憶體洩露
- ArkTS 的記憶體快照與記憶體洩露除錯記憶體洩露除錯
- Netty原始碼學習8——從ThreadLocal到FastThreadLocal(如何讓FastThreadLocal記憶體洩漏doge)Netty原始碼threadAST記憶體
- nodejs爬蟲記憶體洩露排查NodeJS爬蟲記憶體洩露
- Pprof定位Go程式記憶體洩露Go記憶體洩露
- ThreadLocal和ThreadLocalMap原始碼分析thread原始碼
- Flutter引擎原始碼解讀-記憶體管理篇Flutter原始碼記憶體
- Android中使用Handler造成記憶體洩露的分析和解決Android記憶體洩露