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高级架构群备注好信息!

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