Details
-
Bug
-
Status: Closed
-
Critical
-
Resolution: Fixed
-
2.2.2
-
Java 7
Description
Java 7 introduced parallel classloading. There is a map in java.lang.ClassLoader called parallelLockMap. This map holds String keys for class names and the associated Objects used to synchronize on for each class.
Now, I found that dynamically parsing (perhaps also loading?) and unloading classes with GroovyClassLoader leaves obsolete keys in this map. Any time java.lang.ClassLoader#loadClass(..) is called, an entry is created for that class name if it does not exist.
In the default Java delegating classloader paradigm the delegate classloader is asked for a class first, so a call to the root loader's java.lang.ClassLoader#loadClass(..) is made for a whole lot of classes, including Groovy metaclasses, polluting the map with keys.
The entries that remain in the map even after the class and associated metaclasses themselves have been unloaded have the following two types of keys:
"groovy.runtime.metaclass.MyDynamicallyLoadedClassNameMetaClass"
"MyDynamicallyLoadedClassNameBeanInfo"
A key for the class name itself, ie. MyDynamicallyLoadedClassName, does not appear in the map. I am assuming it is never inserted there, but instead defined by GroovyClassLoader without asking the parent first. But these metaclass keys do leak into the map, eventually exhausting heap memory even if the classes themselves get nicely unloaded (from PermGen) when the GroovyClassLoader is GC'ed.
Here's a very simple test script that will eventually run out of memory (it does take some time):
String newClass = "class CLASSNAME {}" while (true) { GroovyClassLoader gcl = new GroovyClassLoader() Class clazz = gcl.parseClass(newClass.replace("CLASSNAME", "NewClass"+System.nanoTime())) clazz.newInstance() }
Be sure to run JVM with
-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
to make sure you run out of heap memory and not PermGen space.
One fix for this would be to add a check to GroovyClassLoader that would prevent it from delegating to the parent first if the class to be loaded is a metaclass of a class loaded by that classloader. I am definitely no expert in this so you might come up with a much better idea.
This has been tested on Groovy 2.0.5 and 2.2.2 and I expect that it affects a lot of versions running on Java 7.
Sure, it doesn't leak memory at a very fast pace, only a ConcurrentHashMap entry for each class loaded and unloaded. I can see how in many applications this is not a major problem, but please consider for example a server that automatically checks students' programming assignments or such. Please do change the issue priority if appropriate.
Attachments
Attachments
Issue Links
- relates to
-
GROOVY-8113 Groovy script/template engine produce memory leak
- Open
-
GROOVY-8189 OutOfMemoryError with groovy.use.classvalue=false
- Open