詳解ThreadLocal為什么會(huì)內(nèi)存溢出原理
前言
關(guān)于ThreadLocal (線程本地存儲(chǔ)),從字面意思上看主要是存儲(chǔ)一些本地變量,使它們能在一個(gè)線程內(nèi)共用,與其他的線程進(jìn)行數(shù)據(jù)隔離,保證了數(shù)據(jù)在一個(gè)線程內(nèi)的安全性,日常的開(kāi)發(fā)中,ThreadLocal的使用場(chǎng)景還是比較常見(jiàn)的,包括登陸信息的token的存儲(chǔ)、連接管理一個(gè)線程持有一個(gè)鏈接,該連接可以在不同的方法之間進(jìn)行傳遞,一個(gè)線程內(nèi)數(shù)據(jù)共享,通過(guò)key,value的形式存儲(chǔ)數(shù)據(jù)。
ThreadLocal源碼分析
ThreadLocal類有一個(gè)靜態(tài)內(nèi)部類,ThreadLocalMap可以看到內(nèi)部有個(gè)Entry 數(shù)組k就是ThreadLocal的引用,Entry 繼承了WeakReference 說(shuō)明Entry 的k是個(gè)弱引用,從這看來(lái)如果是弱引用那么就不會(huì)存在內(nèi)存溢出,GC運(yùn)行的時(shí)候,這個(gè)對(duì)象就會(huì)被回收掉,value則是存儲(chǔ)的對(duì)象,而ThreadLocalMap則是由threadLocals來(lái)創(chuàng)建的,可以看到這兩個(gè)變量的默認(rèn)都是NULL。


p>只有當(dāng)線程第一次調(diào)用的時(shí)候才會(huì)創(chuàng)建它。

ThreadLocal value內(nèi)存溢出
前面講到ThreadLocal的key是threadlocals是弱引用不會(huì)存在內(nèi)存溢出,那么容易存在內(nèi)存溢出的一定是它的value,它與current thread 存在一個(gè)強(qiáng)引用的關(guān)系,導(dǎo)致value無(wú)法進(jìn)行回收,如果線程的對(duì)象一直不去銷毀這個(gè)強(qiáng)引用的對(duì)象,那么導(dǎo)致這個(gè)關(guān)系一直存在就會(huì)出現(xiàn)內(nèi)存溢出,
/**
* 內(nèi)存溢出例子
*/
public class ThreadLocalTest {
static class Mytask{
//定義10m的Byte數(shù)組
private Byte[] bytes =new Byte[10 *1024 * 1024];
}
private static ThreadLocal<Mytask> threadLocal = new ThreadLocal();
public static void main(String[] args) throws InterruptedException {
// 5個(gè)核心線程、5個(gè)最大線程、隊(duì)列長(zhǎng)度100
ThreadPoolExecutor threadPoolExecutor =new ThreadPoolExecutor(5, 5, 60,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
for (int i = 0; i < 10; i++) {
//執(zhí)行任務(wù)
executeTask(threadPoolExecutor);
Thread.sleep(1000);
}
}
private static void executeTask(ThreadPoolExecutor threadPoolExecutor){
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("創(chuàng)建Mytask對(duì)象");
Mytask mytask =new Mytask();
threadLocal.set(mytask);
}
});
}
堆內(nèi)存設(shè)置50m

通過(guò)上面代碼,創(chuàng)建線程池對(duì)創(chuàng)建的任務(wù),放入threadlocal里面,可以看到出現(xiàn)了堆內(nèi)存的溢出,存放的任務(wù)一直在引用沒(méi)有得到釋放導(dǎo)致堆內(nèi)存空間不足。

p>我們?cè)趕et值到threadLocal后面加入finally,調(diào)用它的remove方法來(lái)清除它的內(nèi)存那么就不會(huì)發(fā)生內(nèi)存溢出。

來(lái)看看remove的代碼,可以看到獲取當(dāng)前線程的threadLocals,然后調(diào)用remove方法獲取到全部的Entry數(shù)組,判斷不為空,key也是當(dāng)前的key則調(diào)用clear方法將數(shù)組清除,這樣數(shù)組空間得到了釋放自然就不會(huì)出現(xiàn)內(nèi)存溢出。
public void remove() {
ThreadLocalMap m = getMap(Thread./currentThread/());
if (m != null)
m.remove(this);
}
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;
}
}
}
總結(jié)
ThreadLocal在使用的時(shí)候一定到進(jìn)行remove,這也是一個(gè)比較常見(jiàn)的內(nèi)存溢出的例子,希望大家引以為戒。
以上就是詳解ThreadLocal為什么會(huì)內(nèi)存溢出原理的詳細(xì)內(nèi)容,更多關(guān)于ThreadLocal內(nèi)存溢出的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java國(guó)際化簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家簡(jiǎn)單介紹了Java國(guó)際化的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
java多線程Synchronized實(shí)現(xiàn)可見(jiàn)性原理解析
這篇文章主要介紹了java多線程Synchronized實(shí)現(xiàn)可見(jiàn)性原理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
劍指Offer之Java算法習(xí)題精講二叉樹(shù)專題篇下
跟著思路走,之后從簡(jiǎn)單題入手,反復(fù)去看,做過(guò)之后可能會(huì)忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會(huì)發(fā)現(xiàn)質(zhì)的變化2022-03-03
解決JSON.toJSONString首字母大小寫(xiě)的問(wèn)題
這篇文章主要介紹了解決JSON.toJSONString首字母大小寫(xiě)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
java實(shí)現(xiàn)簡(jiǎn)單的客戶信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單的客戶信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06

