Case and analysis
Problem background
In Tomcat, the following code is all inside the webapp, which will cause WebappClassLoader to leak and cannot be recycled.
public class MyCounter { private int count = 0; public void increment() { count++; } public int getCount() { return count; }}public class MyThreadLocal extends ThreadLocal<MyCounter> {}public class LeakingServlet extends HttpServlet { private static MyThreadLocal myThreadLocal = new MyThreadLocal(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MyCounter counter = myThreadLocal.get(); if (counter == null) { counter = new MyCounter(); myThreadLocal.set(counter); } response.getWriter().println( "The current thread served this servlet " + counter.getCount() + " times"); counter.increment(); }} In the above code, as long as LeakingServlet is called once and the thread executing it is not stopped, WebappClassLoader leak will occur. Every time you reload the application, you will have an additional WebappClassLoader instance, which will eventually lead to PermGen OutOfMemoryException .
Solve the problem
Now let’s think about it: Why does ThreadLocal subclass above cause memory leaks?
WebappClassLoader
First of all, we need to figure out what the hell is WebappClassLoader ?
For web applications running in Java EE containers, the implementation of class loaders is different from that of general Java applications. Different web containers will also be implemented differently. In Apache Tomcat, each web application has a corresponding class loader instance. This class loader also uses proxy mode, the difference is that it first tries to load a certain class, and then proxy to the parent class loader if it cannot be found. This is the opposite of the order of general class loaders. This is a recommended practice in the Java Servlet specification, and its purpose is to make the Web application's own classes prioritize higher than those provided by the Web container. An exception to this proxy pattern is that the classes in the Java core library are not within the scope of the search. This is also to ensure the type safety of the Java core library.
In other words, WebappClassLoader is a custom class loader for Tomcat to load webapps. The class loader of each webapp is different. This is to isolate the classes loaded by different applications.
So what does the features of WebappClassLoader have to do with memory leaks? It is not visible yet, but it is a very important feature that deserves our attention: each webapp has its own WebappClassLoader , which is different from the Java core class loader.
We know that WebappClassLoader leak must be because it is strongly referenced by other objects, so we can try to draw their reference relationship diagram. etc! What exactly does a class loader work? Why is it forced to quote?
Class life cycle and class loader
To solve the above problem, we have to study the relationship between the life cycle of the class and the class loader.
The main thing related to our case is class uninstallation:
After the class is used, if the following situation is met, the class will be uninstalled:
1. All instances of this class have been recycled, that is, no instances of this class exist in the Java heap.
2. ClassLoader loading this class has been recycled.
3. The java.lang.Class object corresponding to this class is not referenced anywhere, and there is no method to access the class through reflection anywhere.
If all the above three conditions are met, the JVM will uninstall the class during garbage collection in the method area. The class uninstallation process is actually to clear the class information in the method area, and the entire life cycle of the Java class ends.
Classes loaded by the class loader that comes with the Java virtual machine will never be uninstalled during the life cycle of the virtual machine. The class loaders that come with Java virtual machines include root class loaders, extension class loaders and system class loaders. The Java virtual machine itself always references these class loaders, and these class loaders always refer to the Class objects of the class they load, so these Class objects are always accessible.
Classes loaded by user-defined class loaders can be unloaded.
Note the above sentence. If WebappClassLoader leaks, it means that the classes it loads cannot be uninstalled, which explains why the above code causes PermGen OutOfMemoryException .
Look at the picture below at the key point
We can find that the class loader object is bidirectionally associated with the Class object it loads. This means that the Class object may be the culprit of the forced reference to WebappClassLoader , causing it to leak.
Quote relationship diagram
After understanding the relationship between the class loader and the life cycle of a class, we can start drawing a reference relationship diagram. ( LeakingServlet.class and myThreadLocal in the figure are not rigorous in the reference drawing, mainly to express that myThreadLocal is a class variable)
Below, we analyze the cause of WebappClassLoader leakage based on the above diagram.
1. LeakingServlet holds static MyThreadLocal , resulting in the life cycle of myThreadLocal as long as the life cycle of LeakingServlet class. This means that myThreadLocal will not be recycled, and weak references are useless, so the current thread cannot clear the counter's strong reference through ThreadLocalMap 's protection measures.
2. Strong reference chain: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader , causing WebappClassLoader to leak.
Summarize
Memory leaks are difficult to detect and are often caused by many reasons. ThreadLocal has become a frequent visitor to memory leaks due to its life cycle bound to threads, and a little carelessness will lead to a disaster. This article is just an analysis of a specific case. If you can use this to apply it to other cases, it would be excellent. Hope this article will be helpful to everyone.