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使用量会有明显增加
    479975-20190420191444643-1060190789.png

  • 堆内存使用情况
    479975-20190420191455216-1480962735.png

  • metaspace使用量一直在增加
    479975-20190420191729421-1046111591.png

  • 类加载情况,total loaded classes一直在增加
    479975-20190420191505402-438274919.png

  • 线程
    479975-20190420191701742-711218922.png

  • dump内存
    479975-20190420192026842-1502805637.png

可见,每次循环中生成的 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

转载于:https://www.cnblogs.com/umgsai/p/10742271.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值