SLF4J MDC内存泄漏

3

我已经搜索了谷歌,查看了多个建议,但没有什么帮助。

我有一个JAX-RS应用程序,使用MDC,在访问端点时设置transactionId以使调试更容易。 但是,当我停止或重新启动Tomcat时,日志中充满了如下条目:

27-Sep-2014 09:42:14.858 SEVERE [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoader.checkThreadLocalMapForLeaks The web application [/core-1.0.0-RC2] created a ThreadLocal with key of type [org.apache.log4j.helpers.ThreadLocalMap] (value [org.apache.log4j.helpers.ThreadLocalMap@464437fc]) and a value of type [java.util.Hashtable] (value [{siteCode=000tst, transactionId=dc8f3a1b-1d7a-4f91-abf6-58d015632d03}]) 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.

我有一个RequestFilter,其中调用了MDC:

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依赖项:

以下是我所需要的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>

如果我有一个ResponseFilter,并使用MDC.clear()方法,这将从MDC中删除值,但似乎无法清除线程:
2014年9月27日09:12:58.216 SEVERE [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoader.checkThreadLocalMapForLeaks 应用程序[/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。
3个回答

2

结合前两个答案的方法,我成功解决了问题。

我使用了log4j实现的MDC而不是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的意思清除MDC。 - Michael-O
它没有起作用。SLF4J MDC是建立在早期版本的log4j之上,在1.2.17修复之前就已经存在了。通过查看实际源代码发现了这一点。我使用的底层日志框架是log4j,因此不需要任何新的依赖项或完整的日志重构。 - Crazy Dino
这就是为什么我建议明确更新log4j的原因。 - Michael-O

1

MDC(Mapped Diagnostic Context)使用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吗? - Crazy Dino
在Servlet 3.0规范之前,我们需要将过滤器配置添加到web.xml文件中。请参考http://stackoverflow.com/questions/11092421/spring-dispatcherservlet-code-to-execute-before-it - Hannes

0

这显然不是一个SLF4J问题,而只是一个Log4J问题。将修复后的Log4J版本作为直接依赖项添加到您的POM中,并设置runtime范围,您的问题应该就会消失。


没有起作用。我正在使用MDC的SLF4j版本,而不是Log4J,并且最新版本的log4j是另一个依赖项的依赖项,因此已经存在。 - Crazy Dino
然后,如果您在请求处理完成后没有清除MDC,则会出现问题。 - Michael-O
我已经清理了MDC,但返回的结果是虽然值已被清除,但threadlocal仍然存在。 - Crazy Dino
尝试使用logback并查看是否仍存在内存泄漏问题。 - Michael-O
添加log4j版本1.2.17的依赖解决了该问题。 - user4446735

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接