在《大話高可用》里,,之前的老大有過總結,,高可用就是:別人死我們不死,自己不作死,,不被別人搞死,。 這段時間,網上都在傳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) { 場景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內存漏洞問題是一道經典面試題啊,。
為了知其然而知其所以然,,從ThreadLocal內存泄露的具體案例出發(fā),,更加仔細的研究了源碼。 ThreadLocal內存泄露問題
原因從debug現(xiàn)象來看,,隨著循環(huán)數i的增長,,t也就是當前線程的threadLocals數隨之增長。 跟蹤threadLocal.set("t1"+i)代碼到set方法的源碼: public void set(T value) { 所以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 { public void put(String key, String val) throws IllegalArgumentException { 簡而言之:一個線程頂多會創(chuàng)建一個ThreadLocal,,所以不會內存泄露。 總結 1,、不要假定,,要證明。證明之后要理解,,邏輯自洽,。 2、別人死我們不死,,自己不作死,,不被別人搞死。 |
|