由於測試環境項目每2小時內存就溢出一次, 分析問題,發現Java動態加載Class並運行那塊存在內存溢出問題, 遂本地調測。
一、找到動態編譯那塊的代碼,具體如下
/**
* @MethodName: 編譯java代碼到Object
* @Description
* @param fullClassName 類名
* @param javaCode 類代碼
* @return Object
* @throws IllegalAccessException
* @throws InstantiationException
*/
public Class javaCodeToObject(String fullClassName, String javaCode) throws IllegalAccessException, InstantiationException {
Object instance = null;
//獲取系統編譯器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 建立DiagnosticCollector對象
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
// 建立用於保存被編譯文件名的對象
// 每個文件被保存在一個從JavaFileObject繼承的類中
ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));
List<JavaFileObject> jfiles = new ArrayList<>();
jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));
//使用編譯選項可以改變默認編譯行爲。編譯選項是一個元素爲String類型的Iterable集合
List<String> options = new ArrayList<>();
options.add("-encoding");
options.add("UTF-8");
options.add("-classpath");
options.add(this.classpath);
//不使用SharedNameTable (jdk1.7自帶的軟引用,會影響GC的回收,jdk1.9已經解決)
options.add("-XDuseUnsharedTable");
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
// 編譯源程序
boolean success = task.call();
if (success) {
//如果編譯成功,用類加載器加載該類
JavaClassObject jco = fileManager.getJavaClassObject();
DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
Class clazz = dynamicClassLoader.loadClass(fullClassName,jco);
try {
dynamicClassLoader.close();
//卸載ClassLoader所加載的類
ClassLoaderUtil.releaseLoader(dynamicClassLoader);
} catch (IOException e) {
e.printStackTrace();
}
return clazz;
} else {
//如果想得到具體的編譯錯誤,可以對Diagnostics進行掃描
String error = "";
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
error = error + compilePrint(diagnostic);
}
}
return null;
}
二、本地寫測試類,並且啓動執行
本地動態加載1000個類,測試查看內存空間變化
public static void main(String[] args) {
String code = "import java.util.HashMap; " +
"import com.yunerp.web.vaadin.message.alert; " +
"import java.util.List; " +
"import java.util.ArrayList; " +
"import com.yunerp.web.vaadin.util.modularfuntion.base.BaseUtil; " +
"import com.yunerp.web.vaadin.util.function.TableFuntionUtil; " +
"import com.yunerp.web.vaadin.util.modularfuntion.stoUtil.StoUtil; " +
"import java.util.Map;import com.yunerp.web.vaadin.util.modularfuntion.user.mini.HomePageUtil; " +
"import com.yunerp.web.util.run.WebInterface; " +
" " +
"public class web2905763164651825363 implements WebInterface { " +
" public Object execute(Map<String,Object> param) { " +
" System.out.println(param.get("key"));" +
" return null; " +
" } " +
"}";
String name = "web2905763164651825363";
for(int i=0;i<1000;i++){
long time1 = System.currentTimeMillis();
DynamicEngine de = DynamicEngine.getInstance();
try {
Class cl = de.javaCodeToObject(name,code);
WebInterface webInterface = (WebInterface)cl.newInstance();
Map<String,Object> param = new HashMap<>();
param.put("key",i);
webInterface.execute(param);
}catch (Exception e) {
e.printStackTrace();
}
System.gc();
long time2 = System.currentTimeMillis();
System.out.println("次數:"+i+" time:"+(time2-time1));
}
}
三、使用JConsole和JVisualVM工具進行檢測。
工具的使用方法:JConsole和JVisualVM工具使用
本地項目啓動後,使用JConsole和 JVisualVM工具進行檢測,發現在動態加載類時, 堆空間內存直線上升,但是所加載的類和實例都被釋放了,而且ClassLoader也釋放了,但是內存還是在 上升,發現結果如下:
在查看堆空間快照的時候,發現JDK自帶的 com.sun.tools.javac.util.SharedNameTable.NameImpl 類及其實例所在的內存空間比達到52%。 具體如下:
四、分析問題
查了很多文獻,也問了很多朋友,都對SharedNameTable這個類很陌生,最終還是在google上找到我想要的解答。
大概意思是:
Java 7引入了這個錯誤:爲了加速編譯,他們引入了SharedNameTable,它使用軟引用來避免重新分配,但不幸的是只會導致JVM膨脹失控,因爲這些軟引用永遠不會被回收直到JVM達到-Xmx內存限制。據稱它將在Java 9中修復。與此同時,還有一個(未記錄的)編譯器選項來禁用它:-XDuseUnsharedTable。
五、 內存溢出問題解決
在編譯選項options中加入 "-XDuseUnsharedTable" ,重新編譯運行,內存溢出問題解決
//使用編譯選項可以改變默認編譯行爲。編譯選項是一個元素爲String類型的Iterable集合
List<String> options = new ArrayList<>();
options.add("-encoding");
options.add("UTF-8");
options.add("-classpath");
options.add(this.classpath);
//不使用SharedNameTable (jdk1.7自帶的軟引用,會影響GC的回收,jdk1.9已經解決)
options.add("-XDuseUnsharedTable");
重新運行的效果圖如下:
至此,問題完美解決。
歡迎加入Java高級架構學習交流羣:375989619
我們提供免費的架構資料 以及免費的解答
不懂得問題都可以來問我們老師,之後還會有職業生涯規劃,以及面試指導
我們每天晚上八點也有公開課免費學習:
10年架構師分享經驗,Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術
加羣條件:
1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的。
2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的。
3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的。
4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的。
5. 羣號:375989619高級架構羣備註好信息!