[ https://issues.apache.org/jira/browse/GROOVY-9068?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16815091#comment-16815091 ]
Daniel Sun edited comment on GROOVY-9068 at 4/11/19 12:33 PM: -------------------------------------------------------------- {{GroovyCodeSource}} object is not cacheable by default, please try with Groovy 2.5.6+ : {code:java} import groovy.transform.CompileStatic import org.codehaus.groovy.runtime.InvokerHelper import org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache import org.codehaus.groovy.runtime.memoize.EvictableCache @CompileStatic class GroovyUtils { private static EvictableCache<String, Class> SCRIPT_CONTENT_CACHE = new ConcurrentCommonCache<>() static eval(String scriptPath, Map bindingMap = [:]) { def scriptClass = SCRIPT_CONTENT_CACHE.getAndPut(scriptPath, { key -> return new GroovyClassLoader().parseClass(GroovyUtils.getResourceAsStream(key).getText('UTF-8')) }) return InvokerHelper.createScript(scriptClass, new Binding(bindingMap)).run() } } {code} P.S. You can use MD5 of script text as cache key. was (Author: daniel_sun): {{GroovyCodeSource}} object is not cacheable by default, please try with Groovy 2.5.6+ : {code:java} import groovy.transform.CompileStatic import org.codehaus.groovy.runtime.InvokerHelper import org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache import org.codehaus.groovy.runtime.memoize.EvictableCache @CompileStatic class GroovyUtils { private static EvictableCache<String, Class> SCRIPT_CONTENT_CACHE = new ConcurrentCommonCache<>() static eval(String scriptPath, Map bindingMap = [:]) { def scriptClass = SCRIPT_CONTENT_CACHE.getAndPut(scriptPath, { key -> return new GroovyClassLoader().parseClass(GroovyUtils.getResourceAsStream(key).getText('UTF-8')) }) return InvokerHelper.createScript(scriptClass, new Binding(bindingMap)).run() } } {code} > GroovyScriptEngine causes Metaspace OOM > --------------------------------------- > > Key: GROOVY-9068 > URL: https://issues.apache.org/jira/browse/GROOVY-9068 > Project: Groovy > Issue Type: Bug > Components: GroovyScriptEngine > Affects Versions: 2.4.9 > Environment: macOS Mojave, MacBook Pro (Retina, 15-inch, Mid 2015) > Reporter: Jingfei Hu > Priority: Major > Labels: GroovyScriptEngineImpl, Metaspace, OOM > > Hello team, > We've encountered this troublesome Metaspace OOM in our application recently > as the number of groovy scripts increases. The groovy usage pattern in our > application is evaluating scripts adhoc and directly. There are no any kinds > of caches. And we use below code to do the evaluation. > > {code:java} > engine.eval(scriptText, bindings); > {code} > We thought the cache of GroovyScriptEngineImpl which is below field would > take effect, but actually not in the multi-threading context > {code:java} > // script-string-to-generated Class map > private ManagedConcurrentValueMap<String, Class> classMap = new > ManagedConcurrentValueMap<String, Class>(ReferenceBundle.getSoftBundle()); > {code} > So without proper cache, our application continuously leverages the groovy > class loader of GroovyScriptEngineImpl to parse scripts to generate Class > objects and fill up Metaspace eventually. > > And below code snippets can easily reproduce this. > > {code:java} > package com.jingfei; > import java.util.concurrent.ExecutorService; > import java.util.concurrent.Executors; > public class Main { > static final GroovyScriptExecutor groovyScriptExecutor = new > GroovyScriptExecutor(); > public static void main(String[] args) throws InterruptedException { > ExecutorService executor = Executors.newFixedThreadPool(10000); > while (true) { > executor.execute(new Runnable() { > @Override > public void run() { > try { > groovyScriptExecutor.executeScript("(1..10).sum()"); > } catch (Exception e) { > e.printStackTrace(); > } > } > }); > } > } > } > package com.jingfei; > import java.util.HashMap; > import java.util.Map; > import javax.script.CompiledScript; > import javax.script.ScriptEngine; > import javax.script.ScriptEngineManager; > import javax.script.ScriptException; > import groovy.lang.GroovyClassLoader; > import org.codehaus.groovy.jsr223.GroovyCompiledScript; > import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl; > /** > * @author jingfei > * @version $Id: GroovyScriptExecutor.java, v 0.1 2019-04-02 20:07 jingfei > Exp $$ > */ > public class GroovyScriptExecutor { > /** Script Engine Manager */ > private static final ScriptEngineManager factory = new > ScriptEngineManager(); > /** Script engine */ > private static final ScriptEngine engine = > factory.getEngineByName("groovy"); > public void executeScript(final String scriptText) throws ScriptException > { > System.out.println(engine.eval(scriptText)); > } > }{code} > Looking into the Metaspace dump, we find out within the Metaspace there are > hundreds of class loader objects named > *groovy/lang/GroovyClassLoader$InnerLoader* and all of Class meta info loaded > by them with the naming pattern *ScriptXXXX.* > > Since the script is all the same, i.e. > {code:java} > (1..10).sum(){code} > , this behavior seems totally odd to me, because I thought due to the > existence of the cache, there would be only one class loader created > necessary to parse the script. > > Any help is appreciated to work out this problem! Thanks! -- This message was sent by Atlassian JIRA (v7.6.3#76005)