Affects: Spring Framework 4.3.4
I use ScriptFactoryPostProcessor
to create Groovy class bean and register in spring context. I have three thousand groovy file need to load.
I debug spring code and found that Spring uses GroovyScriptFactory
to parse groovy class, but every time a new GroovyClassLoader
is used and caues JVM metaspace space error.
/**
* Build a {@link GroovyClassLoader} for the given {@code ClassLoader}.
* @param classLoader the ClassLoader to build a GroovyClassLoader for
* @since 4.3.3
*/
protected GroovyClassLoader buildGroovyClassLoader(ClassLoader classLoader) {
return (this.compilerConfiguration != null ?
new GroovyClassLoader(classLoader, this.compilerConfiguration) : new GroovyClassLoader(classLoader));
}
Issue
why Every time new GroovyScriptFactory
instance to parse groovy?
groovy in jave8 has metaspace bug. see https://stackoverflow.com/questions/37301117/java-groovy-memory-leak-in-groovyclassloader
To solve this bug, we need to use global GroovyClassLoader
instance to parse groovy.
how to solve ?
Comment From: jhoeller
How are you defining those 3000 Groovy scripts? Are you using the XML scripting support or manual GroovyScriptFactory
definitions?
FWIW, this is by far the largest-scale use of Spring's scripting support that I ever heard of...
Comment From: jhoeller
Since the common solution for GroovyShell
usage (as in our GroovyScriptEvaluator
) is to externally set up a common GroovyClassLoader
and pass it in as ClassLoader
to all Groovy accessors, could you potentially apply a similar setup here? In Spring terms, this would mean setting up your ApplicationContext
with context.setClassLoader(new GroovyClassLoader())
or the like, and then rely on Spring's Groovy support to pick up this common GroovyClassLoader
directly.
Note that this won't be working quite yet: We would still have to patch GroovyScriptFactory
to detect such a predetermined GroovyClassLoader
, along the lines of what GroovyScriptEvaluator
effectively does already. At this point I'm just asking whether such a setup would work for you.
Comment From: jhoeller
A quick update: The context-level GroovyClassLoader
arrangement works fine in our tests so far, in combination with a GroovyScriptFactory
patch that detects it in setBeanClassLoader
(in the same fashion as the GroovyShell
constructor does it already). It looks like we'll be going with this solution since it is easy enough to backport to our older branches, not changing the behavior of classic class loader setups (which keep using an isolated GroovyClassLoader
per scripted bean). All it should take is to explicitly configure your ApplicationContext
with a setClassLoader(new GroovyClassLoader())
call, wherever you are creating or customizing the context (e.g. directly on a newly created GenericApplicationContext
instance or in an ApplicationContextInitializer
).
We intend to initially ship this patch in our upcoming 5.2.7 and 5.1.16 releases next week, then subsequently in 5.0.18 and 4.3.28 at a later point.
Comment From: jhoeller
Please note that due to the adaptive GroovyClassLoader
reuse only being available as of Groovy 2.5 - which we've upgraded to in the Spring Framework 5.1 line -, we're not backporting this further than 5.1.x on our end. Since Spring Framework 5.0.x and 4.3.x are reaching their end of life anyway, please upgrade to Spring Framework 5.1+ at your earliest convenience.