网站首页 > 技术文章 正文
由于测试环境项目每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");
重新运行的效果图如下:
至此,问题完美解决。
猜你喜欢
- 2024-09-21 Java并发编程:LongAdder | LongAccumulator 对比测试
- 2024-09-21 「Java技巧」优雅的统计程序的执行时间,别再用System.cur
- 2024-09-21 Flink SQL 知其所以然(九)| SQL 的时间语义
- 2024-09-21 ArrayList插入1000w条数据之后,我怀疑了jvm...
- 2024-09-21 《Java实战之内存模型》详解篇(java内存模型happens before)
- 2024-09-21 比反射更快!使用ASM获取class信息(ClassReader)
- 2024-09-21 了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑
- 2024-09-21 让大学生写的一个计算时间的方法,有人看得出来是在做什么吗?这
- 2024-09-21 Java基础——Java多线程(Lock接口详解)
- 2024-09-21 JVM性能调优监控工具jps、jstack、jmap、jhat、jstat使用详解
- 1514℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 569℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 510℃MySQL service启动脚本浅析(r12笔记第59天)
- 486℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 485℃启用MySQL查询缓存(mysql8.0查询缓存)
- 467℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 446℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 444℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)