专题导航

一、java调用groovy及groovy中如何使用springBean
二、java运行groovy脚本内存问题及解决
三、java运行groovy脚本并发问题及解决
四、java运行groovy工具类

一、问题重现

工具类:

public class GroovyUtil {

    public static Object engine(String filePath, String fileName, Map<String,Object> variable) {
        Object result;
        GroovyScriptEngine engine = null;
        try{
            Binding binding = new Binding();
            variable.entrySet().stream().filter(entry-> StrUtil.isNotBlank(entry.getKey())&& ObjectUtil.isNotEmpty(entry.getValue()))
                    .forEach(entry-> binding.setVariable(entry.getKey(),entry.getValue()));
            engine = new GroovyScriptEngine(filePath);
            result = engine.run(fileName, binding);
        }catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("路径不存在");
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("执行文件发生错误");
        } finally {
            //清除缓存(每次生成的class文件并不一致)
            if (engine!=null){
                engine.getGroovyClassLoader().clearCache();
            }
        }
        return result;
    }

}

每次需要执行groovy脚本的时候,都需要调用engine方法。观察engine.run()方法:

public Object run(String scriptName, Binding binding) throws ResourceException, ScriptException {
        return createScript(scriptName, binding).run();
    }

每次执行都会创建script对象。预想的是每次执行完都清除缓存,保证传入的参数正确。

进行测试:

在这里插入图片描述

通过JProfiler发现在执行这一百次的时候,虚拟机分配最大内存1024M的情况下竟然GC了两次:

在这里插入图片描述

发现GroovyClassLoader().clearCache()并未起作用。

二、优化

问题如上所说,engine.run()方法每次执行都会创建script对象,则考虑把script对象存储起来,不必每次去创建该对象,每次调用方法时,修改script的绑定参数。

public class GroovyUtil {

    private static Map<String, Script> scriptMap = new ConcurrentHashMap<>();

    public static Object engine(String filePath, String fileName, Map<String, Object> variable) {
        Binding binding = new Binding();
        if (CollectionUtil.isNotEmpty(variable)) {
            variable.entrySet().stream().filter(entry -> StrUtil.isNotBlank(entry.getKey()) && ObjectUtil.isNotEmpty(entry.getValue()))
                    .forEach(entry -> binding.setVariable(entry.getKey(), entry.getValue()));
        }
        Script script = getScriptInstance(filePath, fileName);
        script.setBinding(binding);
        return script.run();
    }

    private static Script getScriptInstance(String filePath, String fileName) {
        File file = new File(filePath + File.separator + fileName);
        String md5Hex = DigestUtil.md5Hex(file);
        if (scriptMap.containsKey(md5Hex)) {
            return scriptMap.get(md5Hex);
        } else {
            Script script = null;
            try {
                GroovyScriptEngine engine = new GroovyScriptEngine(filePath);
                script = engine.createScript(fileName, new Binding());
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException("文件不存在");
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("生成script文件失败");
            }
            scriptMap.put(md5Hex, script);
            return script;
        }
    }
}

JProfiler测试:内存几乎无变化

内存逐渐递增是因为项目中其他代码块在运行

在这里插入图片描述

三、继续优化

此时如果文件内容改变,则对文件md5与之前的md5值并不会相同,之钱创建的script并不会继续使用。而将其维持在map中,会影响GC将其回收(可达性)。

public class GroovyUtil {

    private static final Map<String, Md5Script> scriptMap = new ConcurrentHashMap<>();

    public static Object engine(String filePath, String fileName, Map<String, Object> variable) {
        Binding binding = new Binding();
        if (CollectionUtil.isNotEmpty(variable)) {
            variable.entrySet().stream().filter(entry -> StrUtil.isNotBlank(entry.getKey()) && ObjectUtil.isNotEmpty(entry.getValue()))
                    .forEach(entry -> binding.setVariable(entry.getKey(), entry.getValue()));
        }
        Script script = getScriptInstance(filePath, fileName);
        script.setBinding(binding);
        return script.run();
    }

    private static Script getScriptInstance(String filePath, String fileName) {
        String fileAbPath = filePath+File.separator+fileName;
        File file = new File(fileAbPath);
        String md5Hex = DigestUtil.md5Hex(file);

        Md5Script md5Script = scriptMap.getOrDefault(fileAbPath,null);
        if (md5Script!=null&&md5Hex.equals(md5Script.getMd5())) {
            return md5Script.getScript();
        } else {
            Script script = null;
            try {
                GroovyScriptEngine engine = new GroovyScriptEngine(filePath);
                script = engine.createScript(fileName, new Binding());
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException("文件不存在");
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("生成script文件失败");
            }
            scriptMap.put(fileAbPath, new Md5Script(md5Hex,script));
            return script;
        }
    }

    @Data
    private static class Md5Script{
        private String md5;
        private Script script;

        public Md5Script(String md5, Script script) {
            this.md5 = md5;
            this.script = script;
        }
    }

}
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐