久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

ThreadLocal&MDC內存泄漏問題

 編程一生 2022-03-09

《大話高可用》里,,之前的老大有過總結,,高可用就是:別人死我們不死,自己不作死,,不被別人搞死,。

這段時間,網上都在傳Log4j2的lookup遠程執(zhí)行代碼漏洞,。這個漏洞要想造成危害,,基本都是被別人搞死的。因為只有url鏈接或者頁面輸入了可執(zhí)行腳本才會觸發(fā),。

最近在重構《簡明日志規(guī)范》,,就是重構我自己之前開源的一個統(tǒng)一日志的組件。對org.slf4j.MDC不放心,,怕引發(fā)內存泄露等線上長時間運行才產生的問題,。要是有這個問題,比Log4j2的漏洞更不可原諒,。因為這純屬自己作死,。所以做了一個小研究。

MDC(Mapped Diagnostic Context,,映射調試上下文)是日志系統(tǒng)提供的一種方便在多線程條件下記錄日志的功能,。可以理解為一個存儲容器,,原理是將信息存儲在了ThreadLocal里,。ThreadLocal和線程綁定。程序里用的都是線程池,理論上應該不會出現(xiàn)無限膨脹的情況,??倸w還是不放心。

利用周末時間進行了源碼研究和測試,,終于放心了,。

驗證

首先讀了源碼,從MDC跟到ThreadLocal,,反復的找,,沒有找到存儲ThreadLocal相關信息的容器。因為要是有,,且釋放做的不好的話是會無限膨脹的,。先進行了粗淺的理論研究之后。測試驗證一下,。

驗證代碼如下,,實際上MDC.put的內容我放了1萬字,便于確認效果,。

public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 200, 1, TimeUnit.DAYS, new LinkedTransferQueue<>());
for
(long i = 0; i < Long.MAX_VALUE; i++) {
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
try {
MDC.put("k", Thread.currentThread().getName());
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}

}
});
}
}

場景1

將最大線程數調到1W,,內存增長根本停不下來,我的電腦明顯心有余力不足,,沒有達到效果,。《Java線程池總結》有對線程池參數的講解,這里不贅述,。

場景2

核心線程池數10,,最大線程數20,在線程執(zhí)行代碼的結尾增加MDC.clear(),。因為大多數的最佳實踐是結束時都要加,,避免線程復用造成的取出數據不準確。日志不準確是個問題,,但是沒有內存泄露這么可怕,。這里加和不加做對比,驗證如果忘記加會不會對內存造成壓力,。

結果運行的進程占2095.1M時達到穩(wěn)定,。

場景3

上面場景中去掉MDC.clear()。

結果運行的進程占2045.8M時達到穩(wěn)定,。

場景4

核心線程池數1,,最大線程數2,在線程執(zhí)行代碼的結尾增加MDC.clear(),。

結果運行的進程占2116.7M時達到穩(wěn)定,。

場景5

上面場景中去掉MDC.clear(),。

結果運行的進程占2109.3M時達到穩(wěn)定。

場景6

上面場景中修改核心線程池數2,,最大線程數4,。

結果運行的進程占2120.7M時達到穩(wěn)定。

試驗結論

無論是否執(zhí)行MDC.clear(),。最終內存占用都會趨于穩(wěn)定,,無內存泄露。

理論分析

雖然驗證了,,還是不放心,。為什么呢?做了這么久的面試官,,我深知ThreadLocal內存漏洞問題是一道經典面試題啊,。


MDC是基于ThreadLocal做的,ThreadLocal泄露了,,MDC可以幸免嗎,?測試沒有泄露是不是測試姿勢不對?

為了知其然而知其所以然,,從ThreadLocal內存泄露的具體案例出發(fā),,更加仔細的研究了源碼。

ThreadLocal內存泄露問題

下面這段測試代碼在循環(huán)中會出現(xiàn)膨脹,,循環(huán)過多會導致內存泄露,。

public void test() throws Exception {

for (int i = 0; i < 100; i++) {
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set("t1"+i);
Thread t = Thread.currentThread();
log.info(t);
}
}

原因從debug現(xiàn)象來看,,隨著循環(huán)數i的增長,,t也就是當前線程的threadLocals數隨之增長。

跟蹤threadLocal.set("t1"+i)代碼到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);

}

解釋一下:

1>threadLocal獲取到當前線程,,獲取當前線程的ThreadLocalMap對象,。

2>如果map存在,則將當前threadLocal對象作為key設置value="t1"+i

3>如果map不存在,,則新建一個map,,并將當前threadLocal對象作為key設置value="t1"+i

就是說一個線程唯一對應一個ThreadLocalMap對象,。threadLocal對象ThreadLocalMap這個map中的一個key,。將上面的test測試類用偽代碼調整成正常的順序大家應該就能理解了:

public void test() throws Exception {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
for (int i = 0; i < 100; i++) {
ThreadLocal threadLocal = new ThreadLocal();
map.set(threadLocal,"t1"+i);
log.info(t);
}
}

所以threadLocal 的引用一直在當前線程的ThreadLocalMap手里,并不是循環(huán)下一個這個引用就不可達了,。所以會一直創(chuàng)建新的保存老的,,直到內存溢出,如下面運行截圖:

簡而言之: threadLocal使用不當,,可能會內存泄露,。

MDC為什么無內存泄露問題

跟MDC.put("k", Thread.currentThread().getName())到源碼發(fā)現(xiàn)其實put的真正承載容器是static MDCAdapter mdcAdapter,。這是一個靜態(tài)類,在MDC類源碼的底部有mdcAdapter的初始化過程static靜態(tài)塊,,表明了整個JVM只有一份,。

MDCAdapter 是一個接口,具體的實現(xiàn)因為這是slf4j門面中的,。實現(xiàn)會有所不同,,但logback等主流日志實現(xiàn)都是new一個ThreadLocal,將Map容器綁定到ThreadLocal中,。代碼片段感受一下:

public class LogbackMDCAdapter implements MDCAdapter {
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal();
public void put(String key, String val) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} else {
Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
Integer lastOp = this.getAndSetLastOperation(1);
if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) {
oldMap.put(key, val);
} else {
Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
newMap.put(key, val);
}

}

}

簡而言之:一個線程頂多會創(chuàng)建一個ThreadLocal,,所以不會內存泄露。

總結

1,、不要假定,,要證明。證明之后要理解,,邏輯自洽,。

2、別人死我們不死,,自己不作死,,不被別人搞死。

    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多