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.