Search code examples
javatomcatmemory-leaksslf4jmdc

SLF4J MDC Memory Leak


I've had a google for this, looked at multiple suggestions and nothing seems to help.

I have a JAX-RS application which using MDC, when an endpoint is hit sets a transactionId in order to make debugging easier. However, when i stop or restart Tomcat the logs are filled with entries like this:

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.

I have a RequestFilter where MDC is called:

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());


}

These are my sl4fj dependencies:

<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>

If I have a ResponseFilter with MDC.clear() It removes the values from the MDC, but doesn't seem to clear the thread:

27-Sep-2014 09:12:58.216 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@391216c7]) and a value of type [java.util.Hashtable] (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.

Apparently it was fixed in log4j 1.2.17 but the changes don't seem to have filtered through to slf4j.


Solution

  • Using the mix of the previous two answers I was able to fix the problem.

    I used the log4j implementation of MDC rather than SLF4J, and added a ResponseFilter as well to do the clear out. It may, or may not have affected it, but I also used provider annotations rather than stipulating the classes in the web.xml.

    RequestFilter (much the same):

    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());
    
        }
    }
    

    ResponseFilter:

    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>