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;\n" +
"import com.yunerp.web.vaadin.message.alert;\n" +
"import java.util.List;\n" +
"import java.util.ArrayList;\n" +
"import com.yunerp.web.vaadin.util.modularfuntion.base.BaseUtil;\n" +
"import com.yunerp.web.vaadin.util.function.TableFuntionUtil;\n" +
"import com.yunerp.web.vaadin.util.modularfuntion.stoUtil.StoUtil;\n" +
"import java.util.Map;import com.yunerp.web.vaadin.util.modularfuntion.user.mini.HomePageUtil;\n" +
"import com.yunerp.web.util.run.WebInterface;\n" +
"\n" +
"public class web2905763164651825363 implements WebInterface {\n" +
" public Object execute(Map<String,Object> param) {\n" +
" System.out.println(param.get(\"key\"));" +
" return null;\n" +
" }\n" +
"}";
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上找到我想要的解答。具體如下兩個連結
連結:https://stackoverflow.com/questions/14617340/memory-leak-when-using-jdk-compiler-at-runtime
大概意思是:
Java 7引入了這個錯誤:為了加速編譯,他們引入了SharedNameTable,它使用軟引用來避免重新分配,但不幸的是只會導致JVM膨脹失控,因為這些軟引用永遠不會被回收直到JVM達到-Xmx
記憶體限制。據稱它將在Java 9中修復。與此同時,還有一個(未記錄的)編譯器選項來禁用它:-XDuseUnsharedTable
。
參考連結2:https://stackoverflow.com/questions/33548218/memory-leak-in-program-using-compiler-api
五、 記憶體溢位問題解決
在編譯選項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動態編譯優化——URLClassLoader 記憶體洩漏問題解決Java編譯優化記憶體
- 揭露 FileSystem 引起的線上 JVM 記憶體溢位問題JVM記憶體溢位
- Java動態編譯優化——ZipFileIndex記憶體洩漏問題分析解決Java編譯優化Index記憶體
- JVM執行緒和記憶體溢位問題排查思路JVM執行緒記憶體溢位
- Java棧溢位|記憶體洩漏|記憶體溢位Java記憶體溢位
- JVM面試問題系列:深入詳解JVM 記憶體區域及記憶體溢位分析JVM面試記憶體溢位
- vue專案編譯node記憶體溢位Vue編譯記憶體溢位
- JVM——記憶體洩漏與記憶體溢位JVM記憶體溢位
- Java記憶體溢位Java記憶體溢位
- JVM(2)-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- 記一次記憶體溢位問題的排查、分析過程及解決思路記憶體溢位
- 【JVM】JVM 概述、記憶體結構、溢位、調優(基礎結構+StringTable+Unsafe+ByteBuffer)JVM記憶體
- 記憶體和棧溢位問題定位記憶體
- eclipse中啟動專案報記憶體溢位問題通過修改配置解決Eclipse記憶體溢位
- 基礎學習-記憶體溢位問題記憶體溢位
- BufferedImage記憶體洩漏和溢位問題記憶體
- Java記憶體區域與記憶體溢位異常(JVM學習系列1)Java記憶體溢位JVM
- JVM學習-02-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- jvm記憶體設定及記憶體溢位、解決方案JVM記憶體溢位
- JVM基本結構、類載入過程以及執行時記憶體溢位分析JVM記憶體溢位
- 日誌導致jvm記憶體溢位相關問題JVM記憶體溢位
- 記憶體溢位記憶體溢位
- 手動寫java記憶體溢位 java.lang.StackOverflowErrorJava記憶體溢位Error
- 從記憶體洩露、記憶體溢位和堆外記憶體,JVM優化引數配置引數記憶體洩露記憶體溢位JVM優化
- 阿里大佬講解Java記憶體溢位示例(堆溢位、棧溢位)阿里Java記憶體溢位
- Java8虛擬機器(JVM)記憶體溢位實戰Java虛擬機JVM記憶體溢位
- java記憶體溢位和記憶體洩漏的區別Java記憶體溢位
- 對jvm虛擬機器 記憶體溢位的思考JVM虛擬機記憶體溢位
- 深入理解JVM虛擬機器-JVM記憶體區域與記憶體溢位JVM虛擬機記憶體溢位
- JAVA記憶體區域與記憶體溢位異常Java記憶體溢位
- [Java基礎]記憶體洩漏和記憶體溢位Java記憶體溢位
- java向excel 寫入海量資料記憶體溢位問題 解決JavaExcel記憶體溢位
- JVM效能調優,記憶體分析工具JVM記憶體
- 解決SqlServer執行指令碼,檔案過大,記憶體溢位問題SQLServer指令碼記憶體溢位
- 手動寫java記憶體溢位 java.lang.OutOfMemoryError: PermGen spaceJava記憶體溢位Error
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- Java記憶體溢位OutOfMemoryError的產生與排查Java記憶體溢位Error
- 模擬實戰排查堆記憶體溢位(java.lang.OutOfMemoryError: Java heap space)問題記憶體溢位JavaError