用同一个GroovyClassLoader加载的Class真的无法回收吗
Posted wen-pan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用同一个GroovyClassLoader加载的Class真的无法回收吗相关的知识,希望对你有一定的参考价值。
源码地址:https://gitee.com/mr_wenpan/basis-enhance/tree/master/enhance-boot-groovy-engine
一、问题描述
- 最近需要使用到groovy,于是进行了一番调研,看到网上有个很常见的说法,如下:
- 如果一个项目里使用同一个
GroovyClassLoader
来加载groovy脚本(将groovy脚本加载为Class对象并放入方法区)。那么根据Class从方法区被卸载的理论如下:- 该Class 的所有实例都已经被回收
- 加载该类的classLoader已经被回收
- 该Class 没有被引用
- 所以得出结论,如果使用同一个
GroovyClassLoader
来加载groovy脚本,那么这些脚本的Class无法被卸载出方法区,因为加载这些Class的ClassLoader持有着这些Class的引用,所以这些Class无法被卸载。如果频繁的使用GroovyClassLoader
来编译脚本为Class会造成方法区OOM
- 如果一个项目里使用同一个
- 对于以上说法,我个人持怀疑态度,GroovyClassLoader加载的Class真的不能被卸载出方法区吗?所以进行了如下验证
二、验证
- 该试验基于我之前开发的整合groovy脚本组件的基础上进行的,可参考源码:https://gitee.com/mr_wenpan/basis-enhance/tree/master/enhance-boot-groovy-engine,将其中的
org.basis.enhance.groovy.compiler.impl.GroovyCompiler
替换为如下代码就行 - 测试方法:
org.enhance.groovy.api.controller.TestPerformanceController#testCompileDirect
1、验证类
public class GroovyCompiler implements DynamicCodeCompiler
private static final Logger LOG = LoggerFactory.getLogger(GroovyCompiler.class);
// 这里新增一个公用的类加载器,每个脚本都通过这个类加载器来加载
private static GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
@Override
public Class<?> compile(String code, String name)
GroovyClassLoader loader = getGroovyClassLoader();
LOG.warn("Compiling filter: " + name);
return (Class<?>) loader.parseClass(code, name);
@Override
public Class<?> compile(ScriptEntry scriptEntry)
GroovyClassLoader loader = getGroovyClassLoader();
return loader.parseClass(scriptEntry.getScriptContext(),
GroovyCompiler.class.getSimpleName() + scriptEntry.getName());
public GroovyClassLoader getGroovyClassLoader()
// 使用同一个加载器来加载
return groovyClassLoader;
2、压测验证
- 压测工具:jmeter
①、项目启动后方法区Class个数
②、项目启动后方法区内存
③、压测方法区Class个数
④、压测方法区内存
⑤、总结分析
- 通过上面测试结果来看,由同一个
GroovyClassLoader
对象加载到方法区的Class其实是被回收了的,方法区的内存占用也不会持续上升 - 那么是不是说明由一个ClassLoader加载到方法区的Class可以在加载他的ClassLoader未被卸载前,自身可以被从方法区卸载呢?结论是否定的!!!
三、分析总结
由上面的分析可以看到,由同一个GroovyClassLoader
对象加载的Class,在加载他的ClassLoader没有被卸载前,自己竟然可以被卸载出方法区????这是啥原因呢?于是debug了一下源码!!!
1、在加载Class后打印该Class的ClassLoader
Class<?> aClass = dynamicCodeCompiler.compile(scriptEntry);
// 打印加载aClass的类加载器
System.out.println("aClass = " + aClass + " ClassLoader = " + aClass.getClassLoader());
2、debug GroovyClassLoader
的加载过程
- 可以看到虽然使用的是同一个
GroovyClassLoader
来加载脚本为Class,但是GroovyClassLoader对象
里每次加载都会创建一个新的ClassLoader去加载脚本 - 所以并不是
GroovyClassLoader
去加载的脚本,而是由GroovyClassLoader每次都创建的一个类型为【InnerLoader
】的类加载器去加载脚本,所以由【GroovyClassLoader】加载的脚本Class是可以被卸载出方法区的,即使【GroovyClassLoader】没有被卸载,只要加载该Class的【InnerLoader
类加载器】被卸载了就行
以上是关于用同一个GroovyClassLoader加载的Class真的无法回收吗的主要内容,如果未能解决你的问题,请参考以下文章