I have a Java class that is dynamically reloading groovy classes with a custom classloader and I am seeing some strange behaviour with some classes not being collected, but over time it does not leak memory (e.g. perm gen does not continue to grow indefinitely).
In my java code I am loading classes like so (boilerplate stuff removed for simplicity):
Class clazz = groovyClassLoader.loadClass(className, true, false, true);
instance = clazz.newInstance();
And I then reload the groovy classes dynamically by clearing the classloader cache, metaregistry, etc:
for (Class c : groovyClassLoader.getLoadedClasses()){
GroovySystem.getMetaClassRegistry().removeMetaClass(c);
}
groovyClassLoader.clearCache();
Now, if i just loop over this code, constantly loading and then re-loading my groovy classes, I see strange behaviour (my test code is literally just looping over the reload process - its not doing anything with any of the objects created etc, so instance in the code above is just local so should be good for GC).
If i run it, setting maxpermsize to 128m then i get leak behaviour and it OOM permgen errors:
However, if i run it again and increase the maxpermsize to 256m, then all is good and it can run forever (this image is 1 hour, but i have run it over night doing thousands of reloads):
Has anyone come across any similar behaviour? or have any ideas? It also seems strange that in the first example, the memory usage jumps up in steps rather than a steady increase.
The saw-tooth pattern you see is typical for memory being allocated and released all the time. You added code to clear the caches, but that does not automatically imply that the class will be collected. The JVM has the tendency to grow memory quite a bit before even attempting normal garbage collection for longer living objects. Removing classes is done even less, often only during full gc runs. That can lead to the annoying situation that permgen is full and an OOME is thrown, even though classes could have been collected. The exact behavior seems to vary from version to version.
Anyway. Just because the class is not referenced anymore, does not mean it is collected right away. Instead permgen may grow to the maximum to then have classes unloaded.
Besides your loadClass calls possibly causing the creation of a new class and the meta class registry referencing the class somehow, there are more classes with possible references. The callsite caching in Groovy for example involves SoftReferences to classes as well. And if something has been called often enough by Reflection (which Groovy may have to do), there might be helper classes be generated to speed up Reflection. This later one is done by the JDK, not Groovy.
One correction I have to make: meta classes are no real classes and cannot take permgen. But they reference classes which take permgen. So if the metaclass is hard referenced,the class stays. there has been an interesting "feature" in the IBM JDK that did consider a class as unloadable if it is hard referenced, even if the object doing that reference itself is part of a SoftReference.
To explain the behavior above more complete I would need the output of the JVM for class loading and unloading. I would assume the application can in theory run in 128MB. If you look in the 128MB-graph at the low point before 16:17:30 and the one before, you may notice the one before is not as low as the other one. That means the point directly before that time code did unload more classes than the point before. The JVM is free in deciding when to remove a class and does not always remove all classes that in theory could be unloaded. You have to have a trade-off between classes being unloaded when they can and performance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With