JVM调优——Java动态编译过程中的内存溢出问题

Stella981
• 阅读 467

由于测试环境项目每2小时内存就溢出一次, 分析问题,发现Java动态加载Class并运行那块存在内存溢出问题, 遂本地调测。

一、找到动态编译那块的代码,具体如下

  1. /**

  2. * @MethodName : 编译java代码到Object

  3. * @Description

  4. * @param fullClassName 类名

  5. * @param javaCode 类代码

  6. * @return Object

  7. * @throws IllegalAccessException

  8. * @throws InstantiationException

  9. */

  10. public Class javaCodeToObject(String fullClassName, String javaCode) throws IllegalAccessException, InstantiationException {

  11. Object instance = null;

  12. //获取系统编译器

  13. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

  14. // 建立DiagnosticCollector对象

  15. DiagnosticCollector diagnostics = new DiagnosticCollector<>();

  16. // 建立用于保存被编译文件名的对象

  17. // 每个文件被保存在一个从JavaFileObject继承的类中

  18. ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));

  19. List jfiles = new ArrayList<>();

  20. jfiles.add( new CharSequenceJavaFileObject(fullClassName, javaCode));

  21. //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合

  22. List options = new ArrayList<>();

  23. options.add( "-encoding");

  24. options.add( "UTF-8");

  25. options.add( "-classpath");

  26. options.add( this.classpath);

  27. //不使用SharedNameTable (jdk1.7自带的软引用,会影响GC的回收,jdk1.9已经解决)

  28. options.add( "-XDuseUnsharedTable");

  29. JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, diagnostics, options, null, jfiles);

  30. // 编译源程序

  31. boolean success = task.call();

  32. if (success) {

  33. //如果编译成功,用类加载器加载该类

  34. JavaClassObject jco = fileManager.getJavaClassObject();

  35. DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);

  36. Class clazz = dynamicClassLoader.loadClass(fullClassName,jco);

  37. try {

  38. dynamicClassLoader.close();

  39. //卸载ClassLoader所加载的类

  40. ClassLoaderUtil.releaseLoader(dynamicClassLoader);

  41. } catch (IOException e) {

  42. e.printStackTrace();

  43. }

  44. return clazz;

  45. } else {

  46. //如果想得到具体的编译错误,可以对Diagnostics进行扫描

  47. String error = "";

  48. for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {

  49. error = error + compilePrint(diagnostic);

  50. }

  51. }

  52. return null;

  53. }

二、本地写测试类,并且启动执行

本地动态加载1000个类,测试查看内存空间变化

  1. public static void main(String[] args) {

  2. String code = "import java.util.HashMap;\n" +

  3. "import com.yunerp.web.vaadin.message.alert;\n" +

  4. "import java.util.List;\n" +

  5. "import java.util.ArrayList;\n" +

  6. "import com.yunerp.web.vaadin.util.modularfuntion.base.BaseUtil;\n" +

  7. "import com.yunerp.web.vaadin.util.function.TableFuntionUtil;\n" +

  8. "import com.yunerp.web.vaadin.util.modularfuntion.stoUtil.StoUtil;\n" +

  9. "import java.util.Map;import com.yunerp.web.vaadin.util.modularfuntion.user.mini.HomePageUtil;\n" +

  10. "import com.yunerp.web.util.run.WebInterface;\n" +

  11. "\n" +

  12. "public class web2905763164651825363 implements WebInterface {\n" +

  13. " public Object execute(Map<String,Object> param) {\n" +

  14. " System.out.println(param.get(\"key\"));" +

  15. " return null;\n" +

  16. " }\n" +

  17. "}";

  18. String name = "web2905763164651825363";

  19. for(int i=0;i<1000;i++){

  20. long time1 = System.currentTimeMillis();

  21. DynamicEngine de = DynamicEngine.getInstance();

  22. try {

  23. Class cl = de.javaCodeToObject(name,code);

  24. WebInterface webInterface = (WebInterface)cl.newInstance();

  25. Map<String,Object> param = new HashMap<>();

  26. param.put( "key",i);

  27. webInterface.execute(param);

  28. } catch (Exception e) {

  29. e.printStackTrace();

  30. }

  31. System.gc();

  32. long time2 = System.currentTimeMillis();

  33. System.out.println( "次数:"+i+" time:"+(time2-time1));

  34. }

  35. }

三、使用JConsole和JVisualVM工具进行检测。

工具的使用方法:JConsole和JVisualVM工具使用

本地项目启动后,使用JConsole和 JVisualVM工具进行检测,发现在动态加载类时, 堆空间内存直线上升,但是所加载的类和实例都被释放了,而且ClassLoader也释放了,但是内存还是在 上升,发现结果如下:

JVM调优——Java动态编译过程中的内存溢出问题

在查看堆空间快照的时候,发现JDK自带的  com.sun.tools.javac.util.SharedNameTable.NameImpl 类及其实例所在的内存空间比达到52%。  具体如下:

JVM调优——Java动态编译过程中的内存溢出问题

四、分析问题

查了很多文献,也问了很多朋友,都对SharedNameTable这个类很陌生,最终还是在google上找到我想要的解答。具体如下两个链接

链接:https://stackoverflow.com/questions/14617340/memory-leak-when-using-jdk-compiler-at-runtime  

JVM调优——Java动态编译过程中的内存溢出问题

大概意思是:

Java 7引入了这个错误:为了加速编译,他们引入了SharedNameTable,它使用软引用来避免重新分配,但不幸的是只会导致JVM膨胀失控,因为这些软引用永远不会被回收直到JVM达到-Xmx内存限制。据称它将在Java 9中修复。与此同时,还有一个(未记录的)编译器选项来禁用它:-XDuseUnsharedTable

参考链接2:https://stackoverflow.com/questions/33548218/memory-leak-in-program-using-compiler-api

JVM调优——Java动态编译过程中的内存溢出问题

五、 内存溢出问题解决

在编译选项options中加入 "-XDuseUnsharedTable" ,重新编译运行,内存溢出问题解决

  1. //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合

  2. List options = new ArrayList<>();

  3. options. add("-encoding");

  4. options. add("UTF-8");

  5. options. add("-classpath");

  6. options. add(this.classpath);

  7. //不使用SharedNameTable (jdk1.7自带的软引用,会影响GC的回收,jdk1.9已经解决)

  8. options. add("-XDuseUnsharedTable");

重新运行的效果图如下:

JVM调优——Java动态编译过程中的内存溢出问题

至此,问题完美解决。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Easter79 Easter79
2年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
京东云开发者 京东云开发者
6个月前
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这