Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static variables, Tomcat and memory leaks

I am debugging a problem that I've had for years in a Tomcat application - a memory leak caused when restarting an application since the Webapp classloader cannot be GC'd. I've taking snapshots of the heap with JProfiler and it seems that at least some my static variables aren't getting freed up.

Certain classes have a static final member which is initialized when the class is first loaded, and because it's final I can't set it to null on app shutdown.

Are static final variables an anti-pattern in Tomcat, or am I missing something? I've just starting poking around with JProfiler 8 so I may be misinterpreting what the incoming references are telling me.

Cheers!

Luke

like image 265
Luke Kolin Avatar asked Oct 27 '13 15:10

Luke Kolin


3 Answers

It is from a few years ago but this presentation I gave at JavaOne covers this topic exactly. The key steps to find the leak are in slide 11 but there is a lot of background information that might be useful as well.

The short version is:

  • Trigger the leak
  • Force GC
  • Use a profiler to find an instance of org.apache.catalina.loader.WebappClassLoader that has the property started=false
  • Trace the GC roots of that object - those are your leaks

As I note in the presentation, finding the leaks is one thing, finding what triggered them can be a lot harder.

I would recommend running on the latest stable Tomcat version as we are always improving the memory leak detection and prevention code and the warnings and errors that that generates may also provide some pointers.

like image 191
Mark Thomas Avatar answered Sep 20 '22 00:09

Mark Thomas


Static variables should be garbage collected when the class itself is garbage collected, which in turn is the case when its class loader is garbage collected.

You can easily create memory leak by having anything that wasn't loaded by the applications classloader having a reference to any of your classes (or an instance of your classes). Look for things like callback listeners etc. that you didn't remove properly (inner/anonymous classes are easily overlooked).

A single reference to one of your classes prevents its class loader and in turn any class loaded by that class loader to be garbage collected.

Edit, example of leaking an object that prevents GC of all your classes:

MemoryMXBean mx = ManagementFactory.getMemoryMXBean();
NotificationListener nl = new NotificationListener() { ... };
((NotificationEmitter) mx).addNotificationListener(nl, ..., ...);

If you register a listener (NotificationListener here) with an object that exists ouside of your applications scope (MemoryMXBean here), your listener will stay 'live' until its explicitly removed. Since your listener instance hold a reference to its ClassLoader (your application classloader) you have now created a strong reference chain preventing GC of the classloader, and in turn, all the classes it loaded, and through that, any static variables those classes hold.

Edit2: Basically you need to avoid this situation:

[Root ClassLoader]
       |
       v
      [Application ClassLoader]
               |
               v
               (Type loaded by Root).addSomething()

The JVM running the application server has loaded the JRE trough the root class loader (and possibly the application server, too). That means those classes will never become eligible for GC, since there will always be live references to some of them. The application server will load your application in a separate class loader that it will not hold a reference to any longer when your application is redeployed (or at least should). But your application will share all classes from at least the JRE with the application server (at least the JRE, but commonly also the Application Server).

In the hypothetical case when the application server were to create a separate class loader (with no parent, a second root class loader practically) and try to load the JRE a second time (as private to your application) it would cause a lot of problems. Classes intended to be singletons would exists twice, and the two class hierarchies would be incapable of holding any refrences of the other (Caused by the same class loaded by different class loaders beeing different types for the JVM). They couldn't even use java.lang.Object as a reference type for the respective "other" class loaders objects.

like image 37
Durandal Avatar answered Sep 19 '22 00:09

Durandal


This Blog can give you an idea about the memory leak in your application.

like image 24
Debojit Saikia Avatar answered Sep 19 '22 00:09

Debojit Saikia