內(nèi)存泄露的原因找到了,罪魁禍?zhǔn)拙尤皇荍ava TheadLocal
public?class?ThreadPoolDemo?{
????private?static?final?ThreadPoolExecutor?poolExecutor?=?new?ThreadPoolExecutor(5,?5,?1,?TimeUnit.MINUTES,?new?LinkedBlockingQueue<>());
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????for?(int?i?=?0;?i?100;?++i)?{
????????????poolExecutor.execute(new?Runnable()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????ThreadLocal?threadLocal?=?new?ThreadLocal<>();
????????????????????threadLocal.set(new?BigObject());
????????????????????//?其他業(yè)務(wù)代碼
????????????????}
????????????});
????????????Thread.sleep(1000);
????????}
????}
????static?class?BigObject?{
????????//?100M
????????private?byte[]?bytes?=?new?byte[100?*?1024?*?1024];
????}
}
創(chuàng)建一個(gè)核心線程數(shù)和最大線程數(shù)都為10的線程池,保證線程池里一直會(huì)有10個(gè)線程在運(yùn)行。
使用for循環(huán)向線程池中提交了100個(gè)任務(wù)。
定義了一個(gè)ThreadLocal類型的變量,Value類型是大對(duì)象。
每個(gè)任務(wù)會(huì)向threadLocal變量里塞一個(gè)大對(duì)象,然后執(zhí)行其他業(yè)務(wù)邏輯。
由于沒有調(diào)用線程池的shutdown方法,線程池里的線程還是會(huì)在運(yùn)行。
?
ThreadLocal的value值存在哪里?
static?class?ThreadLocalMap?{
????//?定義一個(gè)table數(shù)組,存儲(chǔ)多個(gè)threadLocal對(duì)象及其value值
????private?Entry[]?table;
????ThreadLocalMap(ThreadLocal>?firstKey,?Object?firstValue)?{
????????table?=?new?Entry[INITIAL_CAPACITY];
????????int?i?=?firstKey.threadLocalHashCode?&?(INITIAL_CAPACITY?-?1);
????????table[i]?=?new?Entry(firstKey,?firstValue);
????????size?=?1;
????????setThreshold(INITIAL_CAPACITY);
????}
????//?定義一個(gè)Entry類,key是一個(gè)弱引用的ThreadLocal對(duì)象
????//?value是任意對(duì)象
????static?class?Entry?extends?WeakReference<ThreadLocal>>?{
????????/**?The?value?associated?with?this?ThreadLocal.?*/
????????Object?value;
????????Entry(ThreadLocal>?k,?Object?v)?{
????????????super(k);
????????????value?=?v;
????????}
????}
????//?省略其他
}
ThreadLocal類set方法
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);
????}
????ThreadLocalMap?getMap(Thread?t)?{
????????return?t.threadLocals;
????}
????void?createMap(Thread?t,?T?firstValue)?{
????????t.threadLocals?=?new?ThreadLocalMap(this,?firstValue);
????}
????//?省略其他方法
}
public?class?Thread?implements?Runnable?{
????ThreadLocal.ThreadLocalMap?threadLocals?=?null;
????//省略其他
}
ThreadLocal類get方法
class?ThreadLocal<T>?{
????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();
????}
}
獲取當(dāng)前線程的ThreadLocalMap實(shí)例;
如果不為空,以當(dāng)前ThreadLocal實(shí)例為key獲取value;
如果ThreadLocalMap為空或者根據(jù)當(dāng)前ThreadLocal實(shí)例獲取的value為空,則執(zhí)行setInitialValue();
?
ThreadLocal相關(guān)類的關(guān)系總結(jié)

每個(gè)線程是一個(gè)Thread實(shí)例,其內(nèi)部維護(hù)一個(gè)threadLocals的實(shí)例成員,其類型是ThreadLocal.ThreadLocalMap。
通過實(shí)例化ThreadLocal實(shí)例,我們可以對(duì)當(dāng)前運(yùn)行的線程設(shè)置一些線程私有的變量,通過調(diào)用ThreadLocal的set和get方法存取。
ThreadLocal本身并不是一個(gè)容器,我們存取的value實(shí)際上存儲(chǔ)在ThreadLocalMap中,ThreadLocal只是作為TheadLocalMap的key。
每個(gè)線程實(shí)例都對(duì)應(yīng)一個(gè)TheadLocalMap實(shí)例,我們可以在同一個(gè)線程里實(shí)例化很多個(gè)ThreadLocal來存儲(chǔ)很多種類型的值,這些ThreadLocal實(shí)例分別作為key,對(duì)應(yīng)各自的value,最終存儲(chǔ)在Entry table數(shù)組中。
當(dāng)調(diào)用ThreadLocal的set/get進(jìn)行賦值/取值操作時(shí),首先獲取當(dāng)前線程的ThreadLocalMap實(shí)例,然后就像操作一個(gè)普通的map一樣,進(jìn)行put和get。
?
ThreadLocal內(nèi)存模型原理

線程運(yùn)行時(shí),我們定義的TheadLocal對(duì)象被初始化,存儲(chǔ)在Heap,同時(shí)線程運(yùn)行的棧區(qū)保存了指向該實(shí)例的引用,也就是圖中的ThreadLocalRef。
當(dāng)ThreadLocal的set/get被調(diào)用時(shí),虛擬機(jī)會(huì)根據(jù)當(dāng)前線程的引用也就是CurrentThreadRef找到其對(duì)應(yīng)在堆區(qū)的實(shí)例,然后查看其對(duì)用的TheadLocalMap實(shí)例是否被創(chuàng)建,如果沒有,則創(chuàng)建并初始化。
Map實(shí)例化之后,也就拿到了該ThreadLocalMap的句柄,那么就可以將當(dāng)前ThreadLocal對(duì)象作為key,進(jìn)行存取操作。
圖中的虛線,表示key對(duì)應(yīng)ThreadLocal實(shí)例的引用是個(gè)弱引用。
?
強(qiáng)引用弱引用的概念
static?class?ThreadLocalMap?{
????//?定義一個(gè)Entry類,key是一個(gè)弱引用的ThreadLocal對(duì)象
????//?value是任意對(duì)象
????static?class?Entry?extends?WeakReference<ThreadLocal>>?{
????????/**?The?value?associated?with?this?ThreadLocal.?*/
????????Object?value;
????????Entry(ThreadLocal>?k,?Object?v)?{
????????????super(k);
????????????value?=?v;
????????}
????}
????//?省略其他
}
強(qiáng)引用
弱引用
軟引用
虛引用
?
內(nèi)存泄露是不是弱引用的鍋?
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.? 為了處理非常大和長(zhǎng)期的用途,哈希表?xiàng)l目使用weakreference作為鍵。
?
ThreadLocal最佳實(shí)踐
當(dāng)需要存儲(chǔ)線程私有變量的時(shí)候。
當(dāng)需要實(shí)現(xiàn)線程安全的變量時(shí)。
當(dāng)需要減少線程資源競(jìng)爭(zhēng)的時(shí)候。
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
