前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal导致内存泄漏排查小记

ThreadLocal导致内存泄漏排查小记

作者头像
写一点笔记
发布2022-08-11 16:06:04
7380
发布2022-08-11 16:06:04
举报
文章被收录于专栏:程序员备忘录程序员备忘录

背景描述

公司sso域名变动,所有涉及的产品都要修改相关的配置。配置修改好之后,运行期间发现业务系统不稳定,出现了很多json解析异常。但是随着sso那边问题得到修改,我们自己的产品也逐渐稳定起来,但查看日志发现多条内存泄露的日志,于是本着学习的心态,对具体的原因进行了粗略的分析,最终得出的结论是异常导致threadLocal.remove()方法没有执行,最后内存泄漏了,以下是本人定位问题的过程。

报错日志

代码语言:javascript
复制
6:21:26.656 严重 [Thread-219] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks
The 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.

翻译和分析

这个threadlocal移除不了,直到项目死了都还没移除掉。具体的异常发起者是这个catalina的loader,具体的方法就是checkThreadLocalMapForLeaks (检测线程的threadlocal是否有泄露),大概说一下就是就是说检测这个线程的threadlocal,然后发现线程中的threadlocal有值,然后就抛出了内存泄露这个异常。大概猜测一下应该是是tomcat在处理请求的时候,因为要从线程池中获取线程,然后让这个线程去跑请求,但是通过这个检测方法检测一下,发现当前获取的这个线程的threadLocal没有释放掉。我们当时说threadlocal是一个弱引用,我们说弱引用只会在内存不够的时候,jvm才会回收它。而我们的thredlocal保存的map映射关系就是保存在这里的弱引用中,意思是如果我们不显式的通过remove()方法去移除弱引用中的值,那么就会存在内存泄露的问题。所以这个报错日志的核心就是没有走threadlocal.remove()方法。

定位问题代码

日志收集上,我们采用了之前老员工写的日志切面,大概得代码如下:

代码语言:javascript
复制
@Aspect
public 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() {
    }
//执行之前
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            ActionLog actionLog = new ActionLog();
//设置threadLocal变量
            this.threadLocal.set(actionLog);
        }
    }
//这里回环日志
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
       //这里继续执行我们自己的函数
        Object result = proceedingJoinPoint.proceed();
        ActionLog actionLog = (ActionLog)this.threadLocal.get();
      //这里对threadLocal进行remove操作,这里应该没有执行?
        this.threadLocal.remove();
        //写日志
        this.logService.writeActionLog(actionLog);
        return result;
    }
}

通过查看代码我们知道这块的 this.threadLocal.remove();应该是没有执行的,那么没有执行的原因就是异常了。为此作者编写了如下的代码测试了一下:

代码语言:javascript
复制
public class TestThread extends  Thread{

    private static ThreadLocalmyThreadThreadLocal=new ThreadLocal<>();

    public static void main(String[] args) {
        MyThread thread=new MyThread();
        thread.setName("tianjl");
        //设置threadLocal变量
        myThreadThreadLocal.set(thread);
        try{
            //里边抛异常
            doSomeThing();
            //下边的代码是不执行的,也就是this.threadLocal.remove();不执行
            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("测试异常");
    }
}

执行的效果如下

结论和解决方法

根据SSO的变动我们知道,sso异常导致了线程直接跳出方法,使得函数没有执行threadlocal.remove()方法。造成了threadlocal中的值没有清理,最终导致tomcat在检测线程的threadlocal的时候发现有内存泄露,最后直接抛异常了。具体的解决方法就是将threadlocal.remove()放到finally中去。

代码语言:javascript
复制
@Around("webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    try{
       //这里继续执行我们自己的函数
        Object result = proceedingJoinPoint.proceed();
        ActionLog actionLog = (ActionLog)this.threadLocal.get();
        //写日志
        this.logService.writeActionLog(actionLog);
        return result;
      }finally{
       //这里对threadLocal进行remove操作,这里应该没有执行?
        this.threadLocal.remove();
      }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 写点笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档