1. ThreadLocal導(dǎo)致內(nèi)存泄漏排查小記

        共 4387字,需瀏覽 9分鐘

         ·

        2021-04-02 13:34

        背景描述

              公司sso域名變動(dòng),所有涉及的產(chǎn)品都要修改相關(guān)的配置。配置修改好之后,運(yùn)行期間發(fā)現(xiàn)業(yè)務(wù)系統(tǒng)不穩(wěn)定,出現(xiàn)了很多json解析異常。但是隨著sso那邊問題得到修改,我們自己的產(chǎn)品也逐漸穩(wěn)定起來,但查看日志發(fā)現(xiàn)多條內(nèi)存泄露的日志,于是本著學(xué)習(xí)的心態(tài),對(duì)具體的原因進(jìn)行了粗略的分析,最終得出的結(jié)論是異常導(dǎo)致threadLocal.remove()方法沒有執(zhí)行,最后內(nèi)存泄漏了,以下是本人定位問題的過程。

        報(bào)錯(cuò)日志

        6:21:26.656 嚴(yán)重 [Thread-219] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaksThe web application [ttt] created a ThreadLocal withkey of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal@1201c9a0]) and a value of type[tt.zzz.loghelper.model.ActionLog] (value []) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

        翻譯和分析

             這個(gè)threadlocal移除不了,直到項(xiàng)目死了都還沒移除掉。具體的異常發(fā)起者是這個(gè)catalina的loader,具體的方法就是checkThreadLocalMapForLeaks (檢測(cè)線程的threadlocal是否有泄露),大概說一下就是就是說檢測(cè)這個(gè)線程的threadlocal,然后發(fā)現(xiàn)線程中的threadlocal有值,然后就拋出了內(nèi)存泄露這個(gè)異常。大概猜測(cè)一下應(yīng)該是是tomcat在處理請(qǐng)求的時(shí)候,因?yàn)橐獜木€程池中獲取線程,然后讓這個(gè)線程去跑請(qǐng)求,但是通過這個(gè)檢測(cè)方法檢測(cè)一下,發(fā)現(xiàn)當(dāng)前獲取的這個(gè)線程的threadLocal沒有釋放掉。我們當(dāng)時(shí)說threadlocal是一個(gè)弱引用,我們說弱引用只會(huì)在內(nèi)存不夠的時(shí)候,jvm才會(huì)回收它。而我們的thredlocal保存的map映射關(guān)系就是保存在這里的弱引用中,意思是如果我們不顯式的通過remove()方法去移除弱引用中的值,那么就會(huì)存在內(nèi)存泄露的問題。所以這個(gè)報(bào)錯(cuò)日志的核心就是沒有走threadlocal.remove()方法。

        定位問題代碼

               日志收集上,我們采用了之前老員工寫的日志切面,大概得代碼如下:

        @Aspectpublic class WebLogAspect {    private static final Logger log = LoggerFactory.getLogger(WebLogAspect.class);    private ThreadLocalthreadLocal = new ThreadLocal();    @Autowired    private LogHelperProperties logHelperProperties;    @Autowired    private LogService logService;        public WebLogAspect() {    }
        @Pointcut("@annotation(ttt .tt.loghelper.aspect.WebLog)") public void webLog() { }//執(zhí)行之前 @Before("webLog()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); ActionLog actionLog = new ActionLog();//設(shè)置threadLocal變量 this.threadLocal.set(actionLog);        }    }//這里回環(huán)日志 @Around("webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //這里繼續(xù)執(zhí)行我們自己的函數(shù) Object result = proceedingJoinPoint.proceed(); ActionLog actionLog = (ActionLog)this.threadLocal.get(); //這里對(duì)threadLocal進(jìn)行remove操作,這里應(yīng)該沒有執(zhí)行? this.threadLocal.remove(); //寫日志 this.logService.writeActionLog(actionLog); return result;    }}

               通過查看代碼我們知道這塊的  this.threadLocal.remove();應(yīng)該是沒有執(zhí)行的,那么沒有執(zhí)行的原因就是異常了。為此作者編寫了如下的代碼測(cè)試了一下:

        public class TestThread extends  Thread{
        private static ThreadLocalmyThreadThreadLocal=new ThreadLocal<>();
        public static void main(String[] args) { MyThread thread=new MyThread(); thread.setName("tianjl"); //設(shè)置threadLocal變量 myThreadThreadLocal.set(thread); try{ //里邊拋異常 doSomeThing(); //下邊的代碼是不執(zhí)行的,也就是this.threadLocal.remove();不執(zhí)行 System.out.println(myThreadThreadLocal.get().toString()); myThreadThreadLocal.remove(); }catch (Exception e){ e.printStackTrace(); } //這里可以獲取到本該remove的threadlocal的值 System.out.println(myThreadThreadLocal.get().toString()); }
        private static void doSomeThing() throws Exception { throw new Exception("測(cè)試異常"); }}

        執(zhí)行的效果如下

        結(jié)論和解決方法

               根據(jù)SSO的變動(dòng)我們知道,sso異常導(dǎo)致了線程直接跳出方法,使得函數(shù)沒有執(zhí)行threadlocal.remove()方法。造成了threadlocal中的值沒有清理,最終導(dǎo)致tomcat在檢測(cè)線程的threadlocal的時(shí)候發(fā)現(xiàn)有內(nèi)存泄露,最后直接拋異常了。具體的解決方法就是將threadlocal.remove()放到finally中去。  

        @Around("webLog()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {    try{       //這里繼續(xù)執(zhí)行我們自己的函數(shù)        Object result = proceedingJoinPoint.proceed();        ActionLog actionLog = (ActionLog)this.threadLocal.get();        //寫日志        this.logService.writeActionLog(actionLog);        return result;      }finally{       //這里對(duì)threadLocal進(jìn)行remove操作,這里應(yīng)該沒有執(zhí)行?        this.threadLocal.remove();      }}

        瀏覽 55
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. а中文在线天堂 | AAA一级黄片 | 99热首页 | 怡春院欧美 | 国产 综合 人 综合 |