Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Hopefully tomcat 6.0.24 can detect the leak when the application is stopped: each Thread in the JVM is examined, and the internal structures of the Thread and ThreadLocal classes are introspected to see if either the ThreadLocal instance of or the value bound to it were loaded by the WebAppClassLoader of the application being stopped.

...

Note: this particular leak was actually already cured by previous versions of tomcat, because static references of classes loaded by the webappclassloader are nullified (see later).

Webapp class instance as ThreadLocal value

Suppose that we have the following class in the common classpath (for instance in a jar in tomcat/lib) :

No Format

public class ThreadScopedHolder {
	private final static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();

	public static void saveInHolder(Object o) {
		threadLocal.set(o);
	}

	public static Object getFromHolder() {
		return threadLocal.get();
	}
}

And those 2 classes in the webapp :

No Format

public class MyCounter {
	private int count = 0;

	public void increment() {
		count++;
	}

	public int getCount() {
		return count;
	}
}
public class LeakingServlet extends HttpServlet {

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		MyCounter counter = (MyCounter)ThreadScopedHolder.getFromHolder();
		if (counter == null) {
			counter = new MyCounter();
			ThreadScopedHolder.saveInHolder(counter);
		}

		response.getWriter().println(
				"The current thread served this servlet " + counter.getCount()
						+ " times");
		counter.increment();
	}
}

If the servlet is invoked at least once, the webapp classloader would not be GCed when the app is stopped: since the classloader of ThreadScopedHolder is the common classloader, it remains forever which is as expected. But its ThreadLocal instance has a value bound to it (for the non-terminated thread(s) that served the sevlet), which is an instance of a class loaded by the webapp classloader...

Here again, tomcat 6.0.24 will detect and fix the leak by removing this ThreadLocal reference from each Thread.

No Format

Mar 17, 2010 10:23:13 PM org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
SEVERE: A web application created a ThreadLocal with key of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal@44676e3f]) and a value of type [test.leak.threadlocal.value.MyCounter] (value [test.leak.threadlocal.value.MyCounter@62770d2e]) but failed to remove it when the web application was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed.

Webapp class instance indirectly held through a ThreadLocal value

Suppose we have the same ThreadScopedHolder class (in the common classloader) and MyCounter class in the webapp, but with the following servlet :

No Format

public class LeakingServlet extends HttpServlet {

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		List<MyCounter> counterList = (List<MyCounter>) ThreadScopedHolder
				.getFromHolder();
		MyCounter counter;
		if (counterList == null) {
			counter = new MyCounter();
			ThreadScopedHolder.saveInHolder(Arrays.asList(counter));
		} else {
			counter = counterList.get(0);
		}

		response.getWriter().println(
				"The current thread served this servlet " + counter.getCount()
						+ " times");
		counter.increment();
	}
}

We have more or less the same kind of leak as the previous one, but this time tomcat 6.0.24 does not detect the leak when stopping the application (and does not fix it). The problem is that when it inspects the entries of ThreadLocalMap, it checks whether either the key or the value is an instance of a class loaded by the webapp classloader. Here the key is an instance of ThreadLocal, and the value is an instance of java.util.ArrayList.

The "Find leaks" button in tomcat manager will report the leak when asked :

No Format

The following web applications were stopped (reloaded, undeployed), but their
classes from previous runs are still loaded in memory, thus causing a memory
leak (use a profiler to confirm):
/testWeb

But it does not give any clue about what caused the leak, we would need to make a heapdump and analyse it with some tool like Eclipse MAT.

ThreadLocal pseudo-leak

Threads ContextClassLoader

...