【问题标题】:SLF4J MDC Memory LeakSLF4J MDC 内存泄漏
【发布时间】:2014-11-22 09:25:51
【问题描述】:

我为此使用了谷歌,查看了多个建议,但似乎没有任何帮助。

我有一个使用 MDC 的 JAX-RS 应用程序,当一个端点被命中时会设置一个 transactionId 以便于调试。但是,当我停止或重新启动 Tomcat 时,日志中会充满这样的条目:

27-Sep-2014 09:42:14.858 SEVERE [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoader.checkThreadLocalMapForLeaks Web 应用程序 [/core-1.0.0-RC2] 创建了一个带有密钥的 ThreadLocal [org.apache.log4j.helpers.ThreadLocalMap] 类型的(值 [org.apache.log4j.helpers.ThreadLocalMap@464437fc])和 [java.util.Hashtable] 类型的值(值 [{siteCode=000tst, transactionId =dc8f3a1b-1d7a-4f91-abf6-58d015632d03}])但在 Web 应用程序停止时未能将其删除。线程将随着时间的推移而更新,以避免可能的内存泄漏。

我有一个调用 MDC 的 RequestFilter:

import org.slf4j.MDC;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import java.io.IOException;
import java.util.UUID;

public void filter(ContainerRequestContext containerRequestContext) throws IOException {

    String siteCodeHeader = containerRequestContext.getHeaderString("Site-Code");

    if (siteCodeHeader != null) {
        MDC.put("siteCode", siteCodeHeader);
    } else {
        MDC.put("siteCode", "NULL");
    }
    MDC.put("transactionId", UUID.randomUUID().toString());


}

这些是我的 sl4fj 依赖项:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.7</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.7</version>
</dependency>

如果我有一个带有 MDC.clear() 的 ResponseFilter 它会从 MDC 中删除值,但似乎没有清除线程:

27-Sep-2014 09:12:58.216 SEVERE [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoader.checkThreadLocalMapForLeaks Web 应用程序 [/core-1.0.0-RC2] 创建了一个带有密钥的 ThreadLocal [org.apache.log4j.helpers.ThreadLocalMap] 类型(值 [org.apache.log4j.helpers.ThreadLocalMap@391216c7])和 [java.util.Hashtable] 类型值(值 [{}])但失败在 Web 应用程序停止时将其删除。线程将随着时间的推移而更新,以避免可能的内存泄漏。

显然它已在 log4j 1.2.17 中修复,但更改似乎没有过滤到 slf4j。

【问题讨论】:

    标签: java tomcat memory-leaks slf4j mdc


    【解决方案1】:

    结合使用前两个答案,我能够解决问题。

    我使用 MDC 的 log4j 实现而不是 SLF4J,并添加了一个 ResponseFilter 来进行清除。它可能会,也可能不会影响它,但我也使用了提供者注释,而不是在 web.xml 中规定类。

    RequestFilter(大同小异):

    package com.example.jaxrs;
    
    import org.apache.log4j.MDC;
    
    import javax.ws.rs.container.ContainerRequestContext;
    import javax.ws.rs.container.ContainerRequestFilter;
    import javax.ws.rs.ext.Provider;
    import java.io.IOException;
    import java.util.UUID;
    
    @Provider
    public class TransactionIdentifierRequestFilter implements ContainerRequestFilter {
    
        @Override
        public void filter(ContainerRequestContext containerRequestContext) throws IOException {
    
            String siteCodeHeader = containerRequestContext.getHeaderString("Site-Code");
    
            if (siteCodeHeader != null) {
                MDC.put("siteCode", siteCodeHeader);
            } else {
                MDC.put("siteCode", "NULL");
            }
            MDC.put("transactionId", UUID.randomUUID().toString());
    
        }
    }
    

    响应过滤器:

    package com.example.jaxrs;
    
    import org.apache.log4j.MDC;
    
    import javax.ws.rs.container.ContainerRequestContext;
    import javax.ws.rs.container.ContainerResponseContext;
    import javax.ws.rs.container.ContainerResponseFilter;
    import javax.ws.rs.ext.Provider;
    import java.io.IOException;
    
    @Provider
    public class TransactionIdentifierResponseFilter implements ContainerResponseFilter {
    
        @Override
        public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
            MDC.clear();
        }
    }
    

    web.xml

    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>com.example.jaxrs</param-value>
    </init-param>
    

    【讨论】:

    • 这是错误的方法。您现在已将代码绑定到 Log4J。您应该首先通过 SLF4J means 清除 MDC。
    • 它没有用。 SLF4J MDC 是在 1.2.17 修复之前的旧版本 log4j 上构建的。通过查看实际来源发现了这一点。我用于 slf4j 的底层日志记录框架是 log4j,因此不需要任何新的依赖项等或完整的日志记录大修。
    • 这就是为什么我建议明确更新 log4j。
    【解决方案2】:

    MDC 使用ThreadLocal 保存在当前线程中。如果您在过滤器中添加值,则必须在服务调用后将其删除。

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
       try
       {
    
         //add your mdcs
    
         // proceed along the chain
         chain.doFilter(request, response);
    
      } 
      finally
      {
    
         //remove your mdcs
    
      }
    }
    

    【讨论】:

    • 我创建了一个扩展 ServletContainer 的类,覆盖了 doFilter() 方法并将 Spring 中的 servlet 类设置为我的自定义类,但是 doFilter 似乎没有被命中。我需要以某种方式显式调用 doFilter 吗?
    • 在 servlet 3.0 规范之前,可以将过滤器配置添加到 web.xml 文件中。看看stackoverflow.com/questions/11092421/…
    【解决方案3】:

    这显然不是 SLF4J 问题,而只是 Log4J 问题。使用runtime 范围将固定的 Log4J 版本作为直接依赖项添加到您的 POM,您的问题应该会消失。

    【讨论】:

    • 没用。我使用的是 SLF4j 版本的 MDC 而不是 Log4J,而最新版本的 log4j 是另一个依赖项的依赖项,因此已经存在。
    • 那么您在处理请求后错过了清理 MDC。
    • 我已经清除了 MDC 后返回,虽然值已被清除,但 threadlocal 仍然存在。
    • 尝试 logback 看看是否还有内存泄漏。
    • 添加log4j依赖版本1.2.17解决了问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-08
    • 2013-01-20
    • 2011-10-31
    • 2019-08-10
    • 2013-06-24
    • 2011-03-22
    • 2015-04-20
    相关资源
    最近更新 更多