[ 
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)

Reply via email to