用同一个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真的不能被卸载出方法区吗?所以进行了如下验证

二、验证

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真的无法回收吗的主要内容,如果未能解决你的问题,请参考以下文章

用同一个GroovyClassLoader加载的Class真的无法回收吗

提取groovy jar

Groovy内存机制详解

共享对象,符号,C / C ++ lib链接和加载

c语言动态库的加载问题!!!!请高手指点!!!!!!!!

C#winfrom播放器动态加载歌词