JVM調優——Java動態編譯過程中的內存溢出問題

由於測試環境項目每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也釋放了,但是內存還是在 上升,發現結果如下:

JVM調優——Java動態編譯過程中的內存溢出問題

 

在查看堆空間快照的時候,發現JDK自帶的 com.sun.tools.javac.util.SharedNameTable.NameImpl 類及其實例所在的內存空間比達到52%。 具體如下:

JVM調優——Java動態編譯過程中的內存溢出問題

 

四、分析問題

查了很多文獻,也問了很多朋友,都對SharedNameTable這個類很陌生,最終還是在google上找到我想要的解答。

JVM調優——Java動態編譯過程中的內存溢出問題

 

大概意思是:

Java 7引入了這個錯誤:爲了加速編譯,他們引入了SharedNameTable,它使用軟引用來避免重新分配,但不幸的是只會導致JVM膨脹失控,因爲這些軟引用永遠不會被回收直到JVM達到-Xmx內存限制。據稱它將在Java 9中修復。與此同時,還有一個(未記錄的)編譯器選項來禁用它:-XDuseUnsharedTable。

JVM調優——Java動態編譯過程中的內存溢出問題

 

五、 內存溢出問題解決

在編譯選項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");

重新運行的效果圖如下:

JVM調優——Java動態編譯過程中的內存溢出問題

 

至此,問題完美解決。

歡迎加入Java高級架構學習交流羣:375989619

我們提供免費的架構資料 以及免費的解答 

不懂得問題都可以來問我們老師,之後還會有職業生涯規劃,以及面試指導

我們每天晚上八點也有公開課免費學習:
10年架構師分享經驗,Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術

加羣條件:
1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的。
2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的。
3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的。
4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的。
5. 羣號:375989619高級架構羣備註好信息!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章