近期上線了一個系統,鑑權部分使用了Groovy腳本,示例代碼以下html
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的方法,根據傳進去的參數返回對應的值。java
因爲生產環境流量很大,這段代碼被頻繁執行。測試時的代碼以下node
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觀察JVMweb
CPU使用狀況,能夠看到在每次堆內存擴容的時候,CPU使用量會有明顯增長
express
堆內存使用狀況
緩存
metaspace使用量一直在增長
測試
類加載狀況,total loaded classes一直在增長
spa
線程
線程
dump內存
3d
可見,每次循環中生成的 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
http://www.javashuo.com/article/p-hxnznolo-kh.html
https://www.jianshu.com/p/b1a46cc02377