詳解ThreadLocal為什么會內(nèi)存溢出原理
前言
關(guān)于ThreadLocal (線程本地存儲),從字面意思上看主要是存儲一些本地變量,使它們能在一個線程內(nèi)共用,與其他的線程進行數(shù)據(jù)隔離,保證了數(shù)據(jù)在一個線程內(nèi)的安全性,日常的開發(fā)中,ThreadLocal的使用場景還是比較常見的,包括登陸信息的token的存儲、連接管理一個線程持有一個鏈接,該連接可以在不同的方法之間進行傳遞,一個線程內(nèi)數(shù)據(jù)共享,通過key,value的形式存儲數(shù)據(jù)。
ThreadLocal源碼分析
ThreadLocal類有一個靜態(tài)內(nèi)部類,ThreadLocalMap可以看到內(nèi)部有個Entry 數(shù)組k就是ThreadLocal的引用,Entry 繼承了WeakReference 說明Entry 的k是個弱引用,從這看來如果是弱引用那么就不會存在內(nèi)存溢出,GC運行的時候,這個對象就會被回收掉,value則是存儲的對象,而ThreadLocalMap則是由threadLocals來創(chuàng)建的,可以看到這兩個變量的默認(rèn)都是NULL。
p>只有當(dāng)線程第一次調(diào)用的時候才會創(chuàng)建它。
ThreadLocal value內(nèi)存溢出
前面講到ThreadLocal的key是threadlocals是弱引用不會存在內(nèi)存溢出,那么容易存在內(nèi)存溢出的一定是它的value,它與current thread 存在一個強引用的關(guān)系,導(dǎo)致value無法進行回收,如果線程的對象一直不去銷毀這個強引用的對象,那么導(dǎo)致這個關(guān)系一直存在就會出現(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個核心線程、5個最大線程、隊列長度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對象"); Mytask mytask =new Mytask(); threadLocal.set(mytask); } }); }
堆內(nèi)存設(shè)置50m
通過上面代碼,創(chuàng)建線程池對創(chuàng)建的任務(wù),放入threadlocal里面,可以看到出現(xiàn)了堆內(nèi)存的溢出,存放的任務(wù)一直在引用沒有得到釋放導(dǎo)致堆內(nèi)存空間不足。
p>我們在set值到threadLocal后面加入finally,調(diào)用它的remove方法來清除它的內(nèi)存那么就不會發(fā)生內(nèi)存溢出。
來看看remove的代碼,可以看到獲取當(dāng)前線程的threadLocals,然后調(diào)用remove方法獲取到全部的Entry數(shù)組,判斷不為空,key也是當(dāng)前的key則調(diào)用clear方法將數(shù)組清除,這樣數(shù)組空間得到了釋放自然就不會出現(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在使用的時候一定到進行remove,這也是一個比較常見的內(nèi)存溢出的例子,希望大家引以為戒。
以上就是詳解ThreadLocal為什么會內(nèi)存溢出原理的詳細(xì)內(nèi)容,更多關(guān)于ThreadLocal內(nèi)存溢出的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot使用Redisson實現(xiàn)延遲執(zhí)行的完整示例
這篇文章主要介紹了SpringBoot使用Redisson實現(xiàn)延遲執(zhí)行的完整示例,文中通過代碼示例講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-06-06mybatis-plus 通用字段自動化(如邏輯刪除和更新時間等)
這篇文章主要介紹了mybatis-plus 通用字段自動化(如邏輯刪除和更新時間等),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01java高并發(fā)的volatile與Java內(nèi)存模型詳解
這篇文章主要介紹了java高并發(fā)的volatile與Java內(nèi)存模型,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2021-10-10詳解Spring Boot應(yīng)用的啟動和停止(start啟動)
這篇文章主要介紹了詳解Spring Boot應(yīng)用的啟動和停止(start啟動),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12Springboot如何實現(xiàn)自定義異常數(shù)據(jù)
這篇文章主要介紹了Springboot如何實現(xiàn)自定義異常數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09java實現(xiàn)的連接數(shù)據(jù)庫及模糊查詢功能示例
這篇文章主要介紹了java實現(xiàn)的連接數(shù)據(jù)庫及模糊查詢功能,結(jié)合實例形式分析了java基于jdbc連接數(shù)據(jù)庫及使用LIKE語句實現(xiàn)模糊查詢功能的相關(guān)操作技巧,需要的朋友可以參考下2017-12-12springboot 設(shè)置局域網(wǎng)訪問的實現(xiàn)步驟
Spring Boot是一個開源Java-based框架,用于創(chuàng)建獨立的、生產(chǎn)級別的Spring應(yīng)用,它旨在簡化Spring應(yīng)用的初始搭建及開發(fā)過程,通過提供各種自動配置的starter包,Spring Boot使得項目配置變得簡單快速,感興趣的朋友一起看看吧2024-02-02