記一次使用visualVM分析GroovyClassLoader加載機制導致頻繁gc的性能問題


一、現象描述

    通常使用如下代碼在Java 中執行 Groovy 腳本:

1 GroovyClassLoader groovyLoader = new GroovyClassLoader(); 2 Class<Script> groovyClass = (Class<Script>) groovyLoader.parseClass(groovyScriptFile); 3 Script groovyScript = groovyClass.newInstance();

    時,每次執行groovyLoader.parseClass(groovyScriptFile),Groovy 為了保證每次執行的都是新的腳本內容,會每次生成一個新名字的Class文件對象。但當對同一段腳本每次都執行這個方法時,會導致的現象就是裝載的Class會越來越多,從而導致PermGen占用過高,甚至被用滿。如下圖1所示。

 圖 1

二、現象分析

    visualVM是一款優秀的性能分析工具,可從idea的plugins中下載,具體操作和解釋可以看這篇文章 。

    由於是PermGen區的泄漏,通過visualVM分析發現類加載部分有大量的GroovyClassLoader,如下圖2所示。這時想到有使用兩個服務,內部實現是使用Groovy配置的方式(這里我用Groovy封裝了一個服務提供的微框架)。但這兩個服務其實使用的並不頻繁,所以導致切換后的現象不是雪崩式的集群掛掉,而是逐步導致內存占用升高。

 圖 2

三、現象解決

    定位到問題,事情就好辦了,首先看一下處理Groovy加載執行的代碼;然后為生成的類加一層對象緩存;由於腳本中用到了Binding上下文對象,為了線程安全性,調整執行時的方式。最后問題得到解決。

   1. 原始的調用方式

 1 Object scriptObject = null;  2 try {  3     Binding binding = new Binding();  4     binding.setVariable("context", this.context);  5     binding.setVariable("clientInfo", clientInfo);  6     binding.setVariable("params", params);  7     binding.setVariable("data", data);  8 
 9     GroovyShell shell = new GroovyShell(binding); 10     scriptObject = (Object) shell.evaluate(script); 11 } catch (Throwable t) { 12     log.error("groovy script eval error. script: " + script, t); 13 } 14 
15 return scriptObject;

  2. 為Groovy Script增加緩存

 1 private Map<String, Object> scriptCache = new ConcurrentHashMap<String, Object>();  2 ...  3 
 4 Object scriptObject = null;  5 try {  6     Binding binding = new Binding();  7     binding.setVariable("context", this.context);  8     binding.setVariable("clientInfo", clientInfo);  9     binding.setVariable("params", params); 10     binding.setVariable("data", data); 11 
12     Script shell = null; 13     if (isCached(cacheKey)) { 14         shell = (Script) getCaches().get(cacheKey); 15     } else { 16         shell = new GroovyShell().parse(script); 17  } 18 
19  shell.setBinding(binding); 20     scriptObject = (Object) shell.run(); 21 
22     // clear
23  binding.getVariables().clear(); 24     binding = null; 25 
26     // Cache
27     if (!isCached(cacheKey)) { 28         shell.setBinding(null); 29  getCaches().put(cacheKey, shell); 30  } 31 } catch (Throwable t) { 32     log.error("groovy script eval error. script: " + script, t); 33 } 34 
35 return scriptObject;

  3. 解決Binding的線程安全問題

 1 private Map<String, Object> scriptCache = new ConcurrentHashMap<String, Object>();  2 ...  3 
 4 Object scriptObject = null;  5 try {  6     Binding binding = new Binding();  7     binding.setVariable("context", this.context);  8     binding.setVariable("clientInfo", clientInfo);  9     binding.setVariable("params", params); 10     binding.setVariable("data", data); 11 
12     Script shell = null; 13     if (isCached(cacheKey)) { 14         shell = (Script) getCaches().get(cacheKey); 15     } else { 16         shell = cache(cacheKey, script); 17  } 18 
19     scriptObject = (Object) InvokerHelper.createScript(shell.getClass(), binding).run(); 20 
21     // Cache
22     if (!isCached(cacheKey)) { 23  getCaches().put(cacheKey, shell); 24  } 25 } catch (Throwable t) { 26     log.error("groovy script eval error. script: " + script, t); 27 } 28 
29 return scriptObject;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM