一、动态编译案例

要说动态编译内存泄漏,首先我们先看一个案例(网上搜动态编译的资料是千篇一律,只管实现功能,不管内存泄漏,并且都恬不知耻的标识为原创!!)

Java  URLClassLoader 动态编译案例:https://blog.csdn.net/huangshanchun/article/details/72835647

这篇文章和我google搜的其他文章、资料一样,属于JDK1.6以后的版本。确实能实现动态编译并加载,但是却存在严重的URLClassLoader内存泄漏的问题,并且存在SharedNameTable 和  ZipFileIndex的内存泄漏问题。

其中SharedNameTable问题我已经解决:参考

二、URLClassLoader问题分析和解决

1、问题发现

生产环境JVM的运行情况,OLD区爆满,FULlGC不停的执行,项目大概2小时挂掉了,如下图:

在使用VisualVM和 JProfile 两者工具远程分析 测试环境和生产环境的项目后,转储堆Dump文件,并转存到本地分析。 发现动态编译这块存在URLClassLoader的内存泄漏,如下图所示:

2、问题分析

URLClassLoader占了83%的内存空间,遂研究了一下动态编译这块的代码,原案例代码如下:

import javax.tools.*;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;public class DynamicCompile {private URLClassLoader parentClassLoader;private String classpath;public DynamicCompile() {this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();this.buildClassPath();// 存在动态安装的问题,需要动态编译类路径}private void buildClassPath() {this.classpath = null;StringBuilder sb = new StringBuilder();for (URL url : this.parentClassLoader.getURLs()) {String p = url.getFile();sb.append(p).append(File.pathSeparator); //路径分割符linux为:window系统为;}this.classpath = sb.toString();}/*** 编译出类** @param fullClassName 全路径的类名* @param javaCode      java代码** @return 目标类*/public Class<?> compileToClass(String fullClassName, String javaCode) throws Exception {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));List<JavaFileObject> jfiles = new ArrayList<>();jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));List<String> options = new ArrayList<>();options.add("-encoding");options.add("UTF-8");options.add("-classpath");options.add(this.classpath);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);//加载至内存return dynamicClassLoader.loadClass(fullClassName, jco);} else {for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {String error = compileError(diagnostic);throw new RuntimeException(error);}throw new RuntimeException("compile error");}}private String compileError(Diagnostic diagnostic) {StringBuilder res = new StringBuilder();res.append("LineNumber:[").append(diagnostic.getLineNumber()).append("]\n");res.append("ColumnNumber:[").append(diagnostic.getColumnNumber()).append("]\n");res.append("Message:[").append(diagnostic.getMessage(null)).append("]\n");return res.toString();}
}

URLClassLoader这里使用的是全局变量,并且是获取的当前类的ClassLoader(总的) ,在最后加载完class后,并没有关闭操作

this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();

我想,那么用完之后我给这个parentClassLoader进行close不就解决了?  我想的太简单了。

切忌:此处的URLClassLoader不能关闭,因为用的是当前所在类的ClassLoader,如果你关闭了,那么会导致你当前程序的其他类会ClassNotFoundException

3、问题解决(三种)。

1、因为这里使用的是源代码的内存级动态编译,即:

new CharSequenceJavaFileObject(fullClassName, javaCode)

所以,可以用自定义的FileManager 去获取classLoader  ,参考:https://www.cnblogs.com/whuqin/p/4981948.html

但是这里因为是用的ClassLoader而不是URLClassLoader,其实也没法进行close。具体我没去测试有没有内存泄漏。

2、也可以使用源代码的文件级动态编译,去获取文件对应的URLClassLoader。

3、既然不能关闭全局的ClassLoader,又想用URLClassLoader,看了官网URLClassLoader的API后,想到其实可以自己new 一个URLClassLoader来处理动态编译后的Class加载。 毕竟自己new出来的可以直接关闭,不会影响全局类的加载,具体如下:

package com.yunerp.web.util.run.compile;import org.apache.log4j.Logger;
import sun.misc.ClassLoaderUtil;import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import java.io.File;import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;public class DynamicEngine {private final Logger log = Logger.getLogger(this.getClass().getName());/*** @MethodName : 创建classpath* @Description*/private String buildClassPath() {StringBuilder sb = new StringBuilder();URLClassLoader parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();for (URL url : parentClassLoader.getURLs()) {String p = url.getFile();sb.append(p).append(File.pathSeparator);}return sb.toString();}/*** @param fullClassName 类名* @param javaCode      类代码* @return Object* @throws IllegalAccessException* @throws InstantiationException* @MethodName    : 编译java代码到Object* @Description*/public Class javaCodeToObject(String fullClassName, final String javaCode) throws IllegalAccessException, InstantiationException {DynamicClassLoader dynamicClassLoader = null;ClassFileManager fileManager = null;List<JavaFileObject> jfiles = null;JavaClassObject jco = null;URLClassLoader urlClassLoader = null;try {//获取系统编译器JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();// 建立DiagnosticCollector对象DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();//设置系统属性System.setProperty("useJavaUtilZip", "true");// 建立用于保存被编译文件名的对象// 每个文件被保存在一个从JavaFileObject继承的类中fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));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(buildClassPath());//不使用SharedNameTable (jdk1.7自带的软引用,会影响GC的回收,jdk1.9已经解决)options.add("-XDuseUnsharedTable");//设定使用javaUtilZip,避免zipFileIndex泄漏options.add("-XDuseJavaUtilZip");JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);// 编译源程序boolean success = task.call();if (success) {//如果编译成功,用类加载器加载该类jco = fileManager.getJavaClassObject();URL[] urls = new URL[]{new File("").toURI().toURL()};//获取类加载器(每一个文件一个类加载器)urlClassLoader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());dynamicClassLoader = new DynamicClassLoader(urlClassLoader);Class clazz = dynamicClassLoader.loadClass(fullClassName, jco);return clazz;} else {log.error("编译失败: "+ fullClassName);}} catch (Exception e) {e.printStackTrace();} finally {try {//卸载ClassLoader所加载的类if (dynamicClassLoader != null) {dynamicClassLoader.close();ClassLoaderUtil.releaseLoader(dynamicClassLoader);}if (urlClassLoader != null) {urlClassLoader.close();}if (fileManager != null) {fileManager.flush();fileManager.close();}if (jco != null) {jco.close();}jfiles = null;} catch (Exception e) {e.printStackTrace();}}return null;}
}

重新发布后,测试1天的结果如下:

至此:URLClassLoader问题解决,JVM的 OLD区正常,项目能正常运行一周左右(之前是2-4小时就内存泄漏挂掉了)

补充说明:

1、我这里使用URLClassLoader是new的一个空文件流,为什么选择这么做,因客观原因,必须要用源代码的内存级动态编译,这样我无法获取到文件的具体全路径。

2、其实可以优化的更彻底,即我去除options参数里面的classpath,这样就能不用全局的ClassLoader了,  一般来说,只要配置了环境变量CLASSPATH,项目运行就能获取到,但是不知道是否是服务器环境问题,开发和测试环境Linux没法取到classpath,导致编译失败。所以这里我还是保留了buildClassPath()方法。但是总体效果还是很明显了,虽然我有点强迫症。只能等后续有时间了再去研究了。

3、另外,代码中我加上了关于useJavaUtilZip的配置,以为能解决ZipFileIndex的问题,但是实际上这个问题仍然存在,但是影响不是那么大,等待后续或者其他人来研究了。

4、代码规范我没去格式化了,其实应该进行格式化一下,该封装方法的还是封装一下的好。

5、请各位看官尊重我的劳动成果,如转载,请标明原作地址,并在评论告知我一声,谢谢~

Java动态编译优化——URLClassLoader 内存泄漏问题解决相关推荐

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

    由于测试环境项目每2小时内存就溢出一次, 分析问题,发现Java动态加载Class并运行那块存在内存溢出问题, 遂本地调测. 一.找到动态编译那块的代码,具体如下 /** * @MethodName ...

  2. Android 性能优化之内存泄漏检测以及内存优化(上)

    在 Java 中,内存的分配是由程序完成的,而内存的释放则是由 Garbage Collecation(GC) 完成的,Java/Android 程序员不用像 C/C++ 程序员一样手动调用相关函数来 ...

  3. App优化以及内存泄漏溢出优化

    目录介绍 1.OOM和崩溃优化 1.1 OOM优化 1.2 ANR优化 1.3 Crash优化 2.内存泄漏优化 2.0 动画资源未释放 2.1 错误使用单利 2.2 错误使用静态变量 2.3 han ...

  4. Java应用程序中的内存泄漏和内存管理

    Java平台最突出的功能之一是其自动内存管理. 许多人错误地将此功能转换为Java中没有内存泄漏 . 但是,事实并非如此,我给人的印象是,现代Java框架和基于Java的平台,尤其是Android平台 ...

  5. 监视和检测Java应用程序中的内存泄漏

    因此,您的应用程序内存不足,您日夜不停地分析应用程序,以期捕获对象中的内存漏洞. 后续步骤将说明如何监视和检测您的内存泄漏,以确保您的应用程序安全. 1.怀疑内存泄漏 如果您怀疑有内存泄漏,可以使用一 ...

  6. java 动态编译_老生常谈Java动态编译(必看篇)

    一.动态编译简介 new创建对象是静态加载类,在编译时刻就需要加载所有可能使用到的类. 一百个类,有一个类错了,都无法编译. 通过动态加载类可以解决该问题 二.代码实例 2.1 OfficeBette ...

  7. java动态编译无法导包_java动态编译整个项目,解决jar包找不到问题.doc

    java动态编译整个项目,解决jar包找不到问题.doc 还剩 8页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,喜欢就下载吧,价低环保! 内容要点: 64. } 65. 66. /*** ...

  8. java动态编译类文件并加载到内存中

    如果你想在动态编译并加载了class后,能够用hibernate的数据访问接口以面向对象的方式来操作该class类,请参考这篇博文-http://www.cnblogs.com/anai/p/4270 ...

  9. 趁周末,来学点进阶知识:Java 动态编译

    来源 | https://zhenbianshu.github.io 问题 之前的文章从Spring 的环境到 Spring Cloud 的配置中提到过,我们在使用 Spring Cloud 进行动态 ...

最新文章

  1. opencv函数copyto的用法
  2. 顶尖创新型人才:10种与众不同的行为方式
  3. 控制台应用程序的根目录
  4. AOP与OOP的区别
  5. win32 禁用缩放功能_Firefox 73 将引入全局缩放功能,在所有网站都可适用
  6. C语言 PK 各大编程语言
  7. InnoDB 事务/锁/多版本分析?你了解多少?
  8. [luoguP2679] 子串(DP)
  9. 日志能被截取吗 log4j_Java日志体系居然这么复杂?——架构篇
  10. 问题 C: 判断三角形的性质
  11. poj3268(Silver Cow Party)最短路
  12. [转]Linux下Nagios的安装与配置
  13. apache php 关闭版本号显示,不显示版本号
  14. 怎样获取 keycode/keyascii 码?
  15. MusicStore-1 Code First 通过非迁移实现建立数据库
  16. wireshark抓包分析POP3协议
  17. PS常用的三种抠图方法,能应对99%的抠图场景
  18. python3 QT5 端口转发工具 增加最小化隐藏 托盘显示 点击托盘恢复及托盘菜单
  19. 【卡特兰数】【高精】WZK打雪仗(war)
  20. 失踪61年的上帝之鸟重回美国阿肯色州 (组图)

热门文章

  1. java web项目中对数据库用户名密码加密的一种解决方案
  2. 【Mac工具箱】独孤九剑!一名全栈设计师的Mac工具箱(设计/开发/效率)
  3. CAS学习笔记五:SpringBoot自动/手动配置方式集成CAS单点登出
  4. 《程序员2007增刊——实战Web2.0》精彩预览之“指导篇”
  5. 案例+图解带你一文读懂SVG
  6. [Solved] 升级到最新22H2版本任务栏透明失效 - TranslucentTB
  7. lighttpd/1.4.35 for KSWEB伪静态设置(emlog)
  8. 利用poi操作word文档(针对docx格式)
  9. 软件测试周刊(第34期):一岁有一岁的味道,一站有一站的风景
  10. 零、DMSP/OLS、NPP/VIIRS等夜间灯光数据之GDP空间化的真实GDP计算