I'm loading a large number of Groovy (2.4.6) scripts and running them using GroovyScriptEngineImpl in my Java 8 application and I'm running into a problem after a while.
There is a few things that you need to know :
GroovyScriptEngineImpl
every time I run a scriptGroovyClassLoader
every time I run a scriptI need to do it like that in order to isolate each script in a separate "environment": I'm loading some external JARs in the classloader for some scripts and I don't want other scripts to be able to use the classes in those JARs when they're executed.
My problem comes from the fact that for every script that I run, the GroovyClassLoader
will create a new ScriptXXXX
class and load it, but never unload it.
This results in the number of classes loaded increasing indefinitly and the memory ending up being totally filled.
I have tried a huge amount of various solutions, but none seem to work :
-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC
in the JVM arguments-Dgroovy.use.classvalue=true
in the JVM argumentsScriptXXXX
class as shown here : Groovy Classes not being collected but not signs of memory leak
GroovyClassLoader
GroovyScriptEngineImpl
Here's the "Shortest path to GC" for one of the ScriptXXXX
class in Eclipse Memory Analyzer :
I'm clearly running out of solutions here, and none seem to really work as the class loader always keeps a reference to the classes which are never getting GCed.
If you want to reproduce the issue, here's a code sample :
GroovyScriptEngineImpl se;
while (true)
{
se = new GroovyScriptEngineImpl(new GroovyClassLoader());
CompiledScript script = se.compile("println(\"hello\")");
script.eval(se.createBindings());
}
Thanks
UPDATE : After reading pczeus's reply, I tried limiting the metaspace, and some classes seem to be unloading indeed, and I think that it's the ScriptXXX
classes.
That said, after a few minutes I'm getting Out of Metaspace
errors during the script execution.
Here's the profile I'm getting with the VisualVM :
And the "Path to GC" in Eclipse Memory Analyzer for the ScriptXXX
classes are indeed empty (their's no more instance of the classes), even tho the class is still listed in the histogram.
Use reference objects to avoid memory leaks Using the java. lang. ref package, you can work with the garbage collector in your program. This allows you to avoid directly referencing objects and use special reference objects that the garbage collector easily clears.
Through Unclosed Resources Whenever you create a new connection or open a stream, the JVM allocates memory for these resources. In such cases, if you forget to close these resources can block the memory, reduce performance, and may even result in OutOfMemoryError.
The primary tools for detecting memory leaks are the C/C++ debugger and the C Run-time Library (CRT) debug heap functions. The #define statement maps a base version of the CRT heap functions to the corresponding debug version. If you leave out the #define statement, the memory leak dump will be less detailed.
This definitely appears to be somewhat of an edge case that requires special tuning and possibly choosing the garbage collection scheme used.
Since you are using Java8, and you know you are loading thousands of temporary classes, you should try to tune and limit the amount of MetaSpace
available and tune the frequency of cleanup. Straight from https://blogs.oracle.com/poonam/entry/about_g1_garbage_collector_permanent :
JDK8: Metaspace
In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace. There are some new flags added for Metaspace in JDK 8:
-XX:MetaspaceSize= where is the initial amount of space(the initial high-water-mark) allocated for class metadata (in bytes) that may induce a garbage collection to unload classes. The amount is approximate. After the high-water-mark is first reached, the next high-water-mark is managed by the garbage collector
-XX:MaxMetaspaceSize= where is the maximum amount of space to be allocated for class metadata (in bytes). This flag can be used to limit the amount of space allocated for class metadata. This value is approximate. By default there is no limit set. -XX:MinMetaspaceFreeRatio= where is the minimum percentage of class metadata capacity free after a GC to avoid an increase in the amount of space (high-water-mark) allocated for class metadata that will induce a garbage collection.
-XX:MaxMetaspaceFreeRatio= where is the maximum percentage of class metadata capacity free after a GC to avoid a reduction in the amount of space (high-water-mark) allocated for class metadata that will induce a garbage collection.
By default class metadata allocation is only limited by the amount of available native memory. We can use the new option MaxMetaspaceSize to limit the amount of native memory used for the class metadata. It is analogous to MaxPermSize. A garbage collection is induced to collect the dead classloaders and classes when the class metadata usage reaches MetaspaceSize (12Mbytes on the 32bit client VM and 16Mbytes on the 32bit server VM with larger sizes on the 64bit VMs). Set MetaspaceSize to a higher value to delay the induced garbage collections. After an induced garbage collection, the class metadata usage needed to induce the next garbage collection may be increased.
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