To blog Previous post | Next post
How (not) to create a permgen leak?
The other day I was testing part of our application dealing with our permgen leak detection algos. A particular feature I was struggling with needed a new test case.
After noticing all our tests in the field were too complex – involving heavy lifting around application deployment and classloader initialization, I tossed in a new test similar to the following:
import javassist.ClassPool;
public class MicroGenerator {
public static void main(String[] args) throws Exception {
try {
for (int i = 0; i < 20_000; i++) {
generate("eu.plumbr.demo.Generated" + i);
}
} catch (Error e) {
e.printStackTrace();
}
}
public static Class generate(String name) throws Exception {
ClassPool pool = ClassPool.getDefault();
return pool.makeClass(name).toClass();
}
}
The code snippet is rather straightforward, iterating over a loop and generating classes at the runtime. When facing an Error, the code above is well prepared to catch it and print out the stacktrace.
All the complex magic is done using the javassist library keeping the example short and concise.
Looking at the test, you might expect the code fulfill its duty and die with the familiar “java.lang.OutOfMemoryError: Permgen space” message being caught in the main() method catch() block.
Apparently, the situation is a bit more complex than this – half an hour later I found myself still staring at a somewhat different error message. I must admit I have gotten my fair share of OutOfMemoryErrors during the past years. So I thought I had seen it all. Wrong, this one still caught me off guard:
Exception in thread "main"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
If it looks just like any other OutOfMemoryError to you, pay attention to the details – this error has been processed by the UncaughtExceptionHandler and not by regular control flows. This handler as the name states, will step into play when thread is about to terminate due to an uncaught exception. In such case the Java Virtual Machine will query the thread for its UncaughtExceptionHandler and will invoke the handler’s uncaughtException method.
Did you know that 20% of Java applications have memory leaks? Don’t kill your application – instead find and fix leaks with Plumbr in minutes.
Several debugging sessions later I had found the cause, which is similar to the silent memory failure experienced and described years ago. Namely – the memory is used up to the extent where the JVM fails to create a new OutOfMemoryError() instance, fill its stack trace and send the output to the print stream.
So, when you take the code above and run it with just one small addition initializing the Error object along with its stack and output stream, you will face a different result:
static {
new OutOfMemoryError().printStackTrace();
}
Running the same test now results in a lot more familiar output to your log files:
java.lang.OutOfMemoryError: PermGen space
at javassist.ClassPool.toClass(ClassPool.java:1099)
at javassist.ClassPool.toClass(ClassPool.java:1042)
at javassist.ClassPool.toClass(ClassPool.java:1000)
at javassist.CtClass.toClass(CtClass.java:1224)
at eu.plumbr.demo.MicroGenerator.generate(MicroGenerator.java:24)
at eu.plumbr.demo.MicroGenerator.main(MicroGenerator.java:15)
Moral of the story? The safety nets within JVM go deep. It is darn difficult to outsmart modern JVMs today. But as seen from the above, there are still some corner cases where the safety measures are applied in a way that makes it hard to understand what is actually happening. So hopefully this post saves someone a miserable debugging session. If indeed the case, consider following our performance tuning advice in Twitter.
Note that this experiment was run on a mid-2013 MB Pro, with Oracle Hotspot 1.7.0_51 and 1.7.0_45 using 64bit Mac OS X 10.9.2 with default permgen and heap sizes.