zoukankan      html  css  js  c++  java
  • JVM调优——Java动态编译过程中的内存溢出问题

    由于测试环境项目每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<JavaFileObject> diagnostics = new DiagnosticCollector<>();
    16.  
       
    17.  
      // 建立用于保存被编译文件名的对象
    18.  
      // 每个文件被保存在一个从JavaFileObject继承的类中
    19.  
      ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));
    20.  
       
    21.  
      List<JavaFileObject> jfiles = new ArrayList<>();
    22.  
      jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));
    23.  
       
    24.  
      //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合
    25.  
      List<String> options = new ArrayList<>();
    26.  
      options.add("-encoding");
    27.  
      options.add("UTF-8");
    28.  
      options.add("-classpath");
    29.  
      options.add(this.classpath);
    30.  
      //不使用SharedNameTable (jdk1.7自带的软引用,会影响GC的回收,jdk1.9已经解决)
    31.  
      options.add("-XDuseUnsharedTable");
    32.  
       
    33.  
      JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
    34.  
       
    35.  
      // 编译源程序
    36.  
      boolean success = task.call();
    37.  
       
    38.  
      if (success) {
    39.  
      //如果编译成功,用类加载器加载该类
    40.  
      JavaClassObject jco = fileManager.getJavaClassObject();
    41.  
      DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
    42.  
      Class clazz = dynamicClassLoader.loadClass(fullClassName,jco);
    43.  
      try {
    44.  
      dynamicClassLoader.close();
    45.  
      //卸载ClassLoader所加载的类
    46.  
      ClassLoaderUtil.releaseLoader(dynamicClassLoader);
    47.  
      } catch (IOException e) {
    48.  
      e.printStackTrace();
    49.  
      }
    50.  
      return clazz;
    51.  
      } else {
    52.  
      //如果想得到具体的编译错误,可以对Diagnostics进行扫描
    53.  
      String error = "";
    54.  
      for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
    55.  
      error = error + compilePrint(diagnostic);
    56.  
      }
    57.  
      }
    58.  
      return null;
    59.  
      }

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

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

    1.  
      public static void main(String[] args) {
    2.  
       
    3.  
      String code = "import java.util.HashMap; " +
    4.  
      "import com.yunerp.web.vaadin.message.alert; " +
    5.  
      "import java.util.List; " +
    6.  
      "import java.util.ArrayList; " +
    7.  
      "import com.yunerp.web.vaadin.util.modularfuntion.base.BaseUtil; " +
    8.  
      "import com.yunerp.web.vaadin.util.function.TableFuntionUtil; " +
    9.  
      "import com.yunerp.web.vaadin.util.modularfuntion.stoUtil.StoUtil; " +
    10.  
      "import java.util.Map;import com.yunerp.web.vaadin.util.modularfuntion.user.mini.HomePageUtil; " +
    11.  
      "import com.yunerp.web.util.run.WebInterface; " +
    12.  
      " " +
    13.  
      "public class web2905763164651825363 implements WebInterface { " +
    14.  
      " public Object execute(Map<String,Object> param) { " +
    15.  
      " System.out.println(param.get("key"));" +
    16.  
      " return null; " +
    17.  
      " } " +
    18.  
      "}";
    19.  
      String name = "web2905763164651825363";
    20.  
       
    21.  
      for(int i=0;i<1000;i++){
    22.  
      long time1 = System.currentTimeMillis();
    23.  
      DynamicEngine de = DynamicEngine.getInstance();
    24.  
      try {
    25.  
      Class cl = de.javaCodeToObject(name,code);
    26.  
      WebInterface webInterface = (WebInterface)cl.newInstance();
    27.  
      Map<String,Object> param = new HashMap<>();
    28.  
      param.put("key",i);
    29.  
      webInterface.execute(param);
    30.  
       
    31.  
      }catch (Exception e) {
    32.  
      e.printStackTrace();
    33.  
      }
    34.  
      System.gc();
    35.  
      long time2 = System.currentTimeMillis();
    36.  
      System.out.println("次数:"+i+" time:"+(time2-time1));
    37.  
      }
    38.  
      }

    三、使用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" ,重新编译运行,内存溢出问题解决

    1.  
      //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合
    2.  
      List<String> 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");

    重新运行的效果图如下:

    至此,问题完美解决。

  • 相关阅读:
    Android简介(8H)思维导图
    Android章节练习题及答案
    付费方式选择——选项菜单的创建和使用
    Android——选项菜单的实现
    思维导图——四级词汇2
    jquery:为动态加载的元素绑定事件
    tcp/ip协议详解
    Atom实用插件
    如何让textarea不可拖拽
    移动端网站中手机号唤起拨号界面
  • 原文地址:https://www.cnblogs.com/kekexuanxaun/p/9451179.html
Copyright © 2011-2022 走看看