• Groovy 脚本引发的 Old GC问题


    近期上线了一个系统,鉴权部分使用了Groovy脚本,示例代码如下

    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName("groovy");
    
    String function = String.format("def getTargetParamValue(%s) {return "%s"}", "o", "$o");
    engine.eval(function);
    Invocable invocable = (Invocable) engine;
    
    Object result = invocable.invokeFunction("getTargetParamValue", "test-string");
    System.out.println(result);        
    

    这段代码定义了一个Groovy的方法,根据传进去的参数返回对应的值。

    由于生产环境流量很大,这段代码被频繁执行。测试时的代码如下

    public class ScriptEngineTest {
    
        public static void main(String[] args) {
            ScriptEngineManager factory = new ScriptEngineManager();
            ScriptEngine engine = factory.getEngineByName("groovy");
            //测试时改为死循环
            for (int i = 0;; i++) {
                try {
    
                    String function = String.format("def getTargetParamValue(%s) {return "%s"}", "o", "$o");
                    engine.eval(function);
                    Invocable invocable = (Invocable) engine;
    
                    Object result = invocable.invokeFunction("getTargetParamValue", "test-string");
                    System.out.println(result);
    
                    TimeUnit.MICROSECONDS.sleep(100);
    
                    System.out.println(new Date().toLocaleString());
    
                } catch (Exception e) {
                    String errorMsg = String.format("异常!%s", e.getMessage());
                    System.out.println(errorMsg);
                }
    
            }
        }
    }
    

    模拟生产环境的情况,每秒钟执行10次。通过VusualVM观察JVM

    • CPU使用情况,可以看到在每次堆内存扩容的时候,CPU使用量会有明显增加

    • 堆内存使用情况

    • metaspace使用量一直在增加

    • 类加载情况,total loaded classes一直在增加

    • 线程

    • dump内存

    可见,每次循环中生成的 Groovy method在方法执行完成之后并没有被释放掉,导致metaspace的使用量一直增加,最终撑爆JVM

    针对以上问题,解决方法为每次将生成的方法缓存下了,下次要执行的时候从缓存中取。

    
    private final ConcurrentHashMap<String, Invocable> concurrentHashMap = new ConcurrentHashMap<>();
    
    private Object getInvokeResult(Object targetParam, String paramName, String expression) throws Exception {
        //targetParamClassName="com.umgsai.web.home.vo.NodeVO"
        String targetParamClassName = targetParam.getClass().getName();
        //expression="$nodeVO.bizOwner"
        //paramName="nodeVO"
        String functionKey = String.format("%s_%s_%s", targetParamClassName, paramName, expression);
        functionKey = StringUtil.replaceChars(functionKey, "$", "");
        functionKey = StringUtil.replaceChars(functionKey, ".", "_");
        //functionKey为方法的名称和concurrentHashMap的key,这里需要去掉特殊字符
    
        Invocable invocable = concurrentHashMap.get(functionKey);
        if (invocable != null) {
            //如果缓存中有,直接调用
            return invocable.invokeFunction(functionKey, targetParam);
        }
    
       //如果缓存中没有,生成方法,并且存到concurrentHashMap
        synchronized (lock) {
            invocable = concurrentHashMap.get(functionKey);
            if (invocable == null) {
                String function = String.format("def %s(%s) {return "%s"}", functionKey, paramName, expression);
                engine.eval(function);
                invocable = (Invocable) engine;
                concurrentHashMap.put(functionKey, invocable);
                if (log.isInfoEnabled()) {
                    String msg = String.format("Create new Groovy function, functionKey=%s, paramName=%s, expression=%s",
                            functionKey,
                            paramName, expression);
                    log.info(msg);
                }
            }
        }
    
        if (log.isInfoEnabled()) {
            log.info(String.format("Groovy function concurrentHashMap.size=%d", concurrentHashMap.size()));
        }
    
        return invocable.invokeFunction(functionKey, targetParam);
    }
    

    参考 http://www.zgxue.com/120/1204001.html
    https://www.cnblogs.com/fourspirit/p/4332154.html
    https://www.jianshu.com/p/b1a46cc02377

    转载请注明出处 https://www.cnblogs.com/umgsai/p/10742271.html

  • 相关阅读:
    iOS多线程_06_GCD其它用法
    iOS多线程_05_线程间通信NSThread/GCD
    iOS多线程_04_GCD
    iOS多线程_03_Block
    iOS多线程_02_多线程的安全问题
    iOS多线程_01_简介和NSThread
    shell 根据端口号输出所有的pid
    【java核心36讲】接口和抽象类的区别
    CSS布局
    CSS基础
  • 原文地址:https://www.cnblogs.com/umgsai/p/10742271.html
Copyright © 2020-2023  润新知