日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

inputstreamreader未关闭会导致oom_ThreadLocal 一定会导致内存泄露?

發(fā)布時(shí)間:2025/3/15 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 inputstreamreader未关闭会导致oom_ThreadLocal 一定会导致内存泄露? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在面試的時(shí)候,ThreadLocal作為高并發(fā)常用工具經(jīng)常會(huì)被問(wèn)到。而面試官比較喜歡問(wèn)的問(wèn)題有以下兩個(gè):

1、ThreadLocal是怎么實(shí)現(xiàn)來(lái)保證每個(gè)線程的變量副本的。

2、ThreadLocal的內(nèi)存泄露是怎么產(chǎn)生的,怎么避免內(nèi)存泄露。

首先我們來(lái)看第一個(gè)問(wèn)題,實(shí)際上這個(gè)問(wèn)題主要是想考察候選人是否有閱讀過(guò)ThreadLocal的源碼。當(dāng)然閱讀源碼前我們得了解ThreadLocal是怎么使用的,只有使用過(guò)ThreadLocal我們才能產(chǎn)生對(duì)ThreadLocal是怎么做到的這樣的疑問(wèn)。而帶著問(wèn)題去閱讀源碼才能有一種廓然開朗的感覺(jué)。廢話不多說(shuō),放碼過(guò)來(lái)。

貼上threadlocal官方示例

public?class?ThreadId?{?????//?Atomic?integer?containing?the?next?thread?ID?to?be?assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID?????private?static?final?ThreadLocal?threadId?=?????????new?ThreadLocal()?{ @Override protected Integer initialValue() { return nextId.getAndIncrement();???????? }?????};??????//?Returns?the?current?thread's?unique?ID,?assigning?it?if?necessary?????public?static?int?get()?{?????????return?threadId.get();?????}?}

上面代碼主要通過(guò)從threadlocal得到該線程的id,如果當(dāng)前線程沒(méi)有的時(shí)候則生成一個(gè)。

通過(guò)上述例子我們會(huì)有如下疑問(wèn):

1、為什么ThreadLocal可以獲取到當(dāng)前線程的變量副本?

? ?-> 猜測(cè)ThreadLocal內(nèi)部有一個(gè)Map對(duì)象(Map),key為線程對(duì)象,value為我們存儲(chǔ)的變量。

帶著上述疑問(wèn),我們看下ThreadLocal的源碼實(shí)現(xiàn)。

先看set方法。

public?class?ThreadLocal?{ 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); } }

從getMap方法得知,ThreadLocal內(nèi)部確實(shí)存在一個(gè)Map(ThreadLocalMap),只不過(guò)該map是線程的一個(gè)內(nèi)部屬性。而從createMap方法我們得知ThreadLocalMap是以ThreadLocal作為key,我們提供的值為value。

進(jìn)入到Thread類源碼查看,有兩個(gè)ThreatLocalMap類型的屬性。從注釋可以看出,第一個(gè)是由ThreadLocal類維護(hù)的,第二個(gè)是由InheritableThreadLocal類維護(hù)的。而ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類。

public class?Thread?implements?Runnable?{ /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ......}

ThreadLocalMap內(nèi)部維護(hù)的是一個(gè)Entry數(shù)組,而Entry數(shù)組繼承WeakReference。我們知道,weakReference是當(dāng)jvm內(nèi)存不足時(shí)會(huì)回收只有WeakReference引用的對(duì)象。而ThreadLocalMap這樣做的意圖是為什么呢?這個(gè)問(wèn)題會(huì)跟第二個(gè)疑問(wèn)一起解答。

static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal>> { /** The value associated with this ThreadLocal. */????????????Object?value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } } private Entry[] table; ......}

如果是第一次看ThreadLocal源碼的話,看到這里可能覺(jué)得有點(diǎn)繞,畫個(gè)圖理清一下關(guān)系。

首先創(chuàng)建了一個(gè)ThreadLocal對(duì)象

在一個(gè)線程里調(diào)用ThreadLocal的get方法,假設(shè)是第一次調(diào)用得到了null,這時(shí)我們?cè)谕ㄟ^(guò)數(shù)據(jù)庫(kù)查詢得到value并調(diào)用set方法設(shè)置了進(jìn)去,如果該Thread的threadlocals屬性沒(méi)有被初始化過(guò)則會(huì)執(zhí)行ThreadLocal的createMap方法。

下次該線程執(zhí)行調(diào)用get方法時(shí)就會(huì)從得到先set進(jìn)去的值。

上面沒(méi)有貼出get方法的代碼,但是我們可以猜測(cè)出是通Thread.currentThread().threadLocals.get(this).value獲取到值的。這里就不貼出來(lái)了。


第一個(gè)疑問(wèn)解決了,現(xiàn)在看下面試問(wèn)到的第二個(gè)問(wèn)題。ThreadLocal的內(nèi)存泄露是怎么產(chǎn)生的,怎么避免內(nèi)存泄露?

網(wǎng)上搜ThreadLocal內(nèi)存泄很多文章都會(huì)說(shuō)到ThreadLocal的ThreadLocalMap的Entry數(shù)組繼承的是WeakReference,而WeakReference會(huì)在jvm內(nèi)存不足是回收引用。當(dāng)thread常駐或者使用線程池時(shí)核心線程常駐thread未回收,而ThreadLocal被回收,但是value又是強(qiáng)引用,因此不會(huì)被回收而存在內(nèi)存泄露。當(dāng)我看了網(wǎng)上形形色色的文章都是這樣的描述后仍然云里霧里,而且也不符合我們實(shí)際的使用場(chǎng)景。

產(chǎn)生這個(gè)問(wèn)題主要有以下幾個(gè)方面的原因:

一方面是我們雖然使用過(guò)ThreadLocal,但是對(duì)ThreadLocal會(huì)導(dǎo)致的內(nèi)存泄露問(wèn)題場(chǎng)景不熟悉導(dǎo)致的。

另一方面是我們雖然沒(méi)有正確使用ThreadLocal,但是內(nèi)存泄露問(wèn)題并不足于導(dǎo)致OOM(或者說(shuō)存在內(nèi)存泄露的問(wèn)題,但是并不致命)。

以下分幾個(gè)場(chǎng)景討論ThreadLocal內(nèi)存泄露的問(wèn)題。

場(chǎng)景一:一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)新線程

這種場(chǎng)景下使用ThreadLocal,Thread里的threatLocals變量會(huì)隨著線程的銷毀而銷毀,自然也就不存在內(nèi)存泄露的問(wèn)題了。

場(chǎng)景二:存在強(qiáng)引用的幾個(gè)ThreadLocal?+ 少量核心線程數(shù)的線程池

使用ThreadLocal的場(chǎng)景中,我們多數(shù)都是定義為static類變量,但是并不是意味著只有static修飾才是強(qiáng)引用,只要有被別的類進(jìn)行強(qiáng)引用的都算,只是定義為static類變量是我們比較常用的場(chǎng)景(參照官方例子)。

由于ThreadLocal存在外部強(qiáng)引用,因此ThreadLocalMap的key不會(huì)出現(xiàn)null的情況,而少量核心線程數(shù)意味著變量副本不會(huì)很多。因此內(nèi)存泄露的量為?

s?=?coreThreadNums?*?(objectSize1?+?objectSize2?+?...?+?objectSizeX),?X?= threadLocalNums

?因此當(dāng)線程池核心線程數(shù)不多,threadLocal數(shù)量不多和threadLocal變量副本不大時(shí),雖然存在內(nèi)存泄露,但是卻不足于導(dǎo)致OOM。但是當(dāng)threadLocal變量副本比較大時(shí)內(nèi)存泄露的情況就會(huì)嚴(yán)重,導(dǎo)致OOM的可能性加劇了。

場(chǎng)景三:無(wú)強(qiáng)引用的幾個(gè)ThreadLocal?+ 少量核心線程數(shù)的線程池

解析跟場(chǎng)景二類似,只是threadlocalmap的key會(huì)變?yōu)閚ull,但是value卻不會(huì)被回收。因此還是存在內(nèi)存泄露的情況。

場(chǎng)景二和場(chǎng)景三還有一個(gè)前提條件是線程池運(yùn)行的task對(duì)threadlocal的使用是一次性的。因?yàn)楫?dāng)調(diào)用threadLocal的set、get和remove方法時(shí)會(huì)將threadlocalmap里key為null的entry的value釋放掉(實(shí)際調(diào)用的是ThreadLocalMap的expungeStaleEntry方法,感興趣的去看源碼)。當(dāng)然使用了強(qiáng)引用的ThreadLocal是享受不到該好處的。

場(chǎng)景四:無(wú)強(qiáng)引用的大量ThreadLocal +?少量核心線程數(shù)的線程池

跟場(chǎng)景三類似,只是threadLocal作為threadlocal的key占用的內(nèi)存稍微大了一點(diǎn),

場(chǎng)景五:存在強(qiáng)引用的少量ThreadLocal +?大量核心線程數(shù)的線程池

解析跟場(chǎng)景二類型,由于每個(gè)常駐線程都有副本,因此內(nèi)存泄露的情況加劇了。假設(shè)1000個(gè)核心線程,每個(gè)變量副本大小為1m,2個(gè)threadlocal則導(dǎo)致2g的內(nèi)存泄露。

場(chǎng)景六:無(wú)強(qiáng)引用的少量ThreadLocal?+?大量核心線程數(shù)的線程池

跟場(chǎng)景四類似。

場(chǎng)景七:無(wú)強(qiáng)引用ThreadLocal +?大量常駐線程的線程池服務(wù)于特定任務(wù)

這里的特定任務(wù)是指任務(wù)里都會(huì)用到threadlocal get或set 方法的任務(wù),由于每次都調(diào)用了get或set方法,threadlocalmap會(huì)清理掉key為null的,但是當(dāng)沒(méi)有任務(wù)執(zhí)行時(shí),threadlocalmap的value仍然不會(huì)被回收,存在內(nèi)存泄露。

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadLocalTests { public static void main(String[] args) throws Exception{ final ThreadLocal threadLocal1 = new ThreadLocal<>(); final ThreadLocal threadLocal2 = new ThreadLocal<>(); final ExecutorService threadPool = Executors.newFixedThreadPool(1000); Thread.sleep(10000L); for (int i = 0; i < 1000; i++){ threadPool.submit(() -> { byte[] bytes = new byte[1024 * 1024]; threadLocal1.set(bytes); }); threadPool.submit(() -> { byte[] bytes = new byte[1024 * 1024]; threadLocal2.set(bytes); }); } }}

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadLocalTests { public static void main(String[] args) throws Exception{ final ThreadLocal threadLocal1 = new ThreadLocal<>(); final ThreadLocal threadLocal2 = new ThreadLocal<>(); final ExecutorService threadPool = Executors.newFixedThreadPool(1000); Thread.sleep(10000L); for (int i = 0; i < 1000; i++){ threadPool.submit(() -> { byte[] bytes = new byte[1024 * 1024]; threadLocal1.set(bytes);????????????????threadLocal1.remove(); }); threadPool.submit(() -> { byte[] bytes = new byte[1024 * 1024]; threadLocal2.set(bytes); threadLocal2.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;}

其他場(chǎng)景就不討論了,可自行擴(kuò)展。

這里做個(gè)總結(jié)。

無(wú)論threadlocal數(shù)量多少和常駐線程數(shù)量的多少都會(huì)導(dǎo)致內(nèi)存泄露的問(wèn)題,只是嚴(yán)重程度不同罷了。

那怎樣才是使用ThreadLocal的正確姿勢(shì)而不會(huì)導(dǎo)致內(nèi)存泄露呢?這里舉例zuul的requestContext例子。

zuul的requestContext

public class RequestContext extends ConcurrentHashMap<String, Object> { protected static final ThreadLocal extends RequestContext> threadLocal = new ThreadLocal() { @Override protected RequestContext initialValue() { try { return contextClass.newInstance(); } catch (Throwable e) { throw new RuntimeException(e); } } }; /** * unsets the threadLocal context. Done at the end of the request. */ public void unset() { threadLocal.remove(); }}public class ZuulServlet extends HttpServlet { @Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // Marks this request as having passed through the "Zuul engine", as opposed to servlets // explicitly bound in web.xml, for which requests will not have the same data attached RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally {????????????// 實(shí)際執(zhí)行threadlocal的清理方法 RequestContext.getCurrentContext().unset(); } }}

只要在每次用完threadlocal后執(zhí)行threadlocal的remove方法就可以清除掉變量副本,這樣就不會(huì)產(chǎn)生內(nèi)存泄露了。

ThreadLocalMap的remove方法,調(diào)用ThreadLocal的remove方法實(shí)際上是執(zhí)行了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; } } }

總結(jié)

以上是生活随笔為你收集整理的inputstreamreader未关闭会导致oom_ThreadLocal 一定会导致内存泄露?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 黄色成年人视频 | 人人入人人 | 香港三级日本三级 | 热久久91| 毛片最新网址 | 亚洲成人国产 | 咪咪色在线视频 | 激情视频在线播放 | 欧美黑人性xxx猛交 少妇无套内谢久久久久 | 精品动漫一区二区三区在线观看 | 一区二视频 | 欧美大片一级 | 加勒比波多野结衣 | 欧美精品在线观看视频 | 小毛片| 国产乱码一区二区三区在线观看 | 国产91嫩草 | 久久99精品久久久久久水蜜桃 | 亚欧美精品 | 黄色一级在线视频 | 浴室里强摁做开腿呻吟男男 | 国产在线观看免费视频今夜 | 黄色一级网 | 欧美1区2区| 欧美成人片在线 | 欧美日韩不卡视频 | 天天看夜夜爽 | 欧美一区二区成人 | 久久久久久国产视频 | www.69视频 | 免费国偷自产拍精品视频 | 久久精品无码一区二区三区毛片 | 成人免费一级视频 | 91色视频在线 | 曰批又黄又爽免费视频 | 91精品久久久久久久久久入口 | 巨乳女教师的诱惑 | 一本不卡| 国产蜜臀在线 | av日韩一区二区三区 | 麻豆视频在线观看 | 美女黄视频网站 | 91啪国产| 国产精品色在线 | 臭脚猛1s民工调教奴粗口视频 | 久久白浆 | 草久久久 | 中出av在线| 精品动漫3d一区二区三区免费版 | 午夜大片网| 精品视频一区二区三区四区 | 厨房性猛交hd | 国产chinesehd精品| 欧美xxxxx自由摘花 | 国产精品成人av久久 | 国产页| 色呦呦入口 | 美女扒开内裤让男人捅 | 欧美黄色大片免费观看 | av直播在线观看 | 欧美精品日韩在线 | 国产精品.com| 亚洲深夜福利视频 | 国产在线精品一区 | 最新理伦片eeuss影院 | 国产调教av | 性做久久久久久久久 | 少妇人妻真实偷人精品视频 | 浴室里强摁做开腿呻吟男男 | 日韩精品视频一区二区三区 | 精品熟妇视频一区二区三区 | 做爰视频毛片视频 | 91美女在线视频 | 国产又黄又大又粗视频 | 苍井空亚洲精品aa片在线播放 | 粗大黑人巨茎大战欧美成人 | 亚洲人xxxx | 国产av一区二区三区最新精品 | 久久黄色录像 | 精品视频一区二区三区在线观看 | 手机av免费看 | 日韩网站在线观看 | 蜜臀av午夜精品 | 爆操91 | 午夜小视频在线观看 | 视频久久精品 | 自拍露脸高潮 | 亚洲欧美激情另类 | av毛片观看| 中国少妇乱子伦视频播放 | 91精品国产99久久久久久红楼 | 99性趣网| 国产aa | 亚洲第一av网 | 亚洲精品91天天久久人人 | 黄色高清免费 | 狠狠干精品 | 五月天婷婷丁香花 | 热热色av |