tomcat关闭应用时的清理工作(3): ThreadLocal

tomcat在关闭时,可能看到与ThreadLocal相关的警告:

Jan 24, 2014 7:18:52 AM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
SEVERE: The web application [] created a ThreadLocal with key of type 
[java.lang.ThreadLocal] (value [java.lang.ThreadLocal@4e61bc49]) and a value of type 
[com.alibaba.xxx.Entry] (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.

用下面的例子来模拟一下

@WebServlet(name = "MainServlet", urlPatterns = { "/main" }, loadOnStartup = 1)
public class MainServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    private static ThreadLocal<MyClass> tl = new ThreadLocal<MyClass>();

    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        MyClass m = tl.get();
        if (m == null) {
            tl.set(new MyClass());
        }
    }

}

class MyClass {}

请求一次之后,通过脚本会命令停止tomcat,会看到类似的日志:

七月 16, 2014 5:01:35 下午 org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
严重: The web application [] created a ThreadLocal with key of type [java.lang.ThreadLocal] 
(value [java.lang.ThreadLocal@7da150]) and a value of type [org.r113.servlet3.MyClass] 
(value [org.r113.servlet3.MyClass@37e98b70]) 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.

这个泄露其实是可能造成classloader的泄露,因为ThreadLocal引用了自定义的类MyClass,绑定到了当前的请求线程上,而请求线程又是线程池里的线程,生存周期可能会比较长。比如上面模拟的情况,要停止应用的时候,请求线程的ThreadLocal仍未释放,那么即使加载MyClass类的classLoader已经不会再被任何地方使用,可以被垃圾回收了,却因为这个MyClass被引用而得不到回收。

tomcat关闭应用时的清理工作(3): ThreadLocal》上有10条评论

  1. web

    博主你好,最近线上也遇到了这个问题

    Nov 04, 2014 1:21:50 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
    SEVERE: The web application [] created a ThreadLocal with key of type [com.alibaba.dubbo.rpc.RpcContext$1] (value [com.alibaba.dubbo.rpc.RpcContext$1@5c08b06e]) and a value of type [com.alibaba.dubbo.rpc.RpcContext] (value [com.alibaba.dubbo.rpc.RpcContext@3e7734f6]) 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.
    是dubbo的

    ThreadLocal仍未释放这类问题有什么解决方法吗

    回复
    1. hongjiang 文章作者

      在我的例子里,应该在 servlet 的 doGet 或 doPost 方法体里 :
      try{
      // doSomething()
      } finally {
      tl.remove();
      }

      你的具体场景我不清楚,但 dubbo 的 RpcContext 是有 remove 方法的,得看看你在哪儿使用了 RpcContext.getContext() 并 set 了属性,相应的地方,结束时要调用 RpcContext.remove

      回复
      1. web

        在dubbo的 com / alibaba / dubbo / rpc / filter / ContextFilter.java里发现了这个RpcContext.removeContext()

        50 RpcContext.getContext()
        51 .setInvoker(invoker)
        52 .setInvocation(invocation)
        53 .setAttachments(attachments)
        54 .setLocalAddress(invoker.getUrl().getHost(),
        55 invoker.getUrl().getPort());
        56 if (invocation instanceof RpcInvocation) {
        57 ((RpcInvocation)invocation).setInvoker(invoker);
        58 }
        59 try {
        60 return invoker.invoke(invocation);
        61 } finally {
        62 RpcContext.removeContext();
        63 }

        看来确实是释放了,但是为什么tomcat还是不能关闭

        回复
          1. hongjiang 文章作者

            这里要弄清楚, dubbo.rpc.RpcContext.LOCAL 这个ThreadLocal 只是在dubbo的线程上被使用,还是(错误的)被用到了 tomcat 的线程上去了?你的dubbo是怎么启动的?

          2. web

            Spring的Schema扩展里定义了dubbo
            类似于这样

            然后

            <bean id="remoteService" class="本地接口实现"

            都是基于根据官方文档配置的,不知道这样配置对吗?

      1. ch

        我在每次请求结束之后,监听请求结束的事件,调用一次RpcContext.removeContext();暂时解决了这个问题

        回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注