天天看點

java運作groovy腳本記憶體問題及解決

專題導航

一、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對象。預想的是每次執行完都清除緩存,保證傳入的參數正确。

進行測試:

java運作groovy腳本記憶體問題及解決

通過JProfiler發現在執行這一百次的時候,虛拟機配置設定最大記憶體1024M的情況下竟然GC了兩次:

java運作groovy腳本記憶體問題及解決

發現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測試:記憶體幾乎無變化

記憶體逐漸遞增是因為項目中其他代碼塊在運作
java運作groovy腳本記憶體問題及解決

三、繼續優化

此時如果檔案内容改變,則對檔案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;
        }
    }

}