51 java.lang.Class/java.lang.ClassLoader/InstanceKlass/ClassloaderData 的卸载

Posted 蓝风9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了51 java.lang.Class/java.lang.ClassLoader/InstanceKlass/ClassloaderData 的卸载相关的知识,希望对你有一定的参考价值。

前言

之前 碰到了一个 flink 基于 ChildFirstClassLoader 来进行任务隔离 导致的内存泄漏的问题 

然后 使用 demo 复现了一下 问题 

之后 想探索一下 java language 中 类型的卸载相关 

并且会 拓展一些其他的知识 

以下测试用例基于 jdk8, 部分截图基于 jdk9 

测试用例

Test29MultiLoaderContextInvoker 驱动类 

/**
 * Test29MultiLoaderContextInvoker
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2021-12-19 09:31
 */
public class Test29MultiLoaderContextInvoker 

    // Test29MultiLoaderContextInvoker
    // -Xmx10M -XX:+UseSerialGC -XX:+TraceClassLoading
    // -Xmx10M -XX:+UseSerialGC -XX:+TraceClassUnloading
    public static void main(String[] args) throws Exception 

        ClassLoader appClassloader = Test29MultiLoaderContextInvoker.class.getClassLoader();
        String[] alwaysParentPatterns = new String[];
        URL[] classpathes = new URL[]
                new File("/Users/jerry/IdeaProjects/HelloWorld/target/classes").toURI().toURL()
        ;
        Consumer<Throwable> throwableConsumer = (ex) -> 
            ex.printStackTrace();
        ;

        int loopCount = 20;
        for (int i = 0; i < loopCount; i++) 
            ChildFirstClassLoader classLoader = new ChildFirstClassLoader(classpathes, appClassloader, alwaysParentPatterns, throwableConsumer);
            Class mainClass = classLoader.loadClass("com.hx.test12.Test29MultiLoaderContextMain");
            Method mainMethod = mainClass.getDeclaredMethod("main", int.class, String[].class);
            mainMethod.invoke(null, i, args);
        

    


Test29MultiLoaderContextMain 业务类

/**
 * Test29MultiLoaderContextInvoker
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2021-12-19 09:31
 */
public class Test29MultiLoaderContextMain 

    // hold 1M
    public static byte[] dummyBytes = new byte[1 * 1024 * 1024];

    // Test29MultiLoaderContextInvoker
    public static void main(int idx, String[] args) throws Exception 
        System.out.println(String.format(" Test29MultiLoaderContextMain.main be invoked, idx : %s, args : %s ", idx, args));
        new Thread(() -> 
            try 
                System.out.println(String.format(" bizThread - %s is started ", idx));
                Thread.sleep(10_000);
                System.out.println(String.format(" bizThread - %s is end ", idx));
             catch (Exception e) 
                e.printStackTrace();
            
        ).start();
    


执行结果如下 

可以看到 应该是第五次启动了之后, 发生了一次 full gc, 卸载了一部分类型, 然后 后面还是发生了 OOM,  一个 Test29MultiLoaderContextMain.class 会占用 1M 的空间 

6 个占用了 6M, 然后 第七次 invoke 的时候发生了 OOM 

假设我们去掉 Thread.sleep(10_000) 

可以看到的是 20次 invoke 均成功执行了, 这次执行里面 我们发现有对 Test29MultiLoaderContextMain.class 的卸载 

jls 中类型的卸载  

 12.7. Unloading of Classes and Interfacesd 

 可以按到的是 类型卸载的前提是 加载它的 classloader 被gc了 

对应于我们这里, 要想卸载 Test29MultiLoaderContextMain.class 就得保证 加载他的 ChilldFirstClassLoader 被卸载了 

​​​​​​

HotSpotVM 中的类型卸载 

以下运行时截图 基于 jdk9, 调试基于 SerialGC 

这里是 输出 "unloading class com.hx.test12.Test29MultiLoaderContextMain" 打印的地方 

堆栈信息提上来一点, 看到这里有一个 data->is_alive(is_alive_closure) 的判断, 判断的就是 对应的 classloader 是否被 gc 标记(还存活)

当然 这里仅仅是一些标记操作, 真真的 clean 操作是在 gc 之后, 清理掉这些 classloaderData, InstanceKlass 什么的 

java.lang.ClassLoader 的创建和回收

我们这里讨论的是 我们自定义的业务的 ClassLoader 

创建是在 new ChildFirstClassloader 的时候 

回收则是 gc 来处理的 

不可到达 就被回收了 

classLoaderData 的创建和回收 

它的创建是 在 resolve InstanceKlass 的时候, 一个 classloader 对应于一个 classLoaderData, 基于 new 分配 

classLoaderData 的清理是在标记完之后, gc 之后, 进行 delete 的 

InstanceKlass 的创建和回收

注意这里的 new 后面的参数, loader_data, size 

klass 是重写了 operator new, 实际分配的空间是属于 loader_data.metaspace.class_vsm, 是属于我们常说到的 Metaspace[元空间] 

之所以 要提到这个, 是因为 空间的回收和这个是有关系的 

InstanceKlass 的回收, 是 classLoaderData 回收的一部分 

回收 metaspace 之前, 如下, 一切 都很正常 

delete metaspace 之后, 可以看到 相关字段都更新为了 dummy word, 可以大致判断出的是 InstanceKlass 被回收了 

具体填充 dummy word 的地方在这里, 回收 chunk 的地方 

java.lang.Class 的创建和回收

是在创建 InstanceKlass, 初始化的时候 创建的 java.lang.Class 

回收 则是 gc 来处理的 

不可到达, 就被 回收了 

以上几种 分配/回收 模式 

从上面可以看到, 有几种内存的 分配, 回收的方式 

1. 基于 c++ 原生的 new, delete 

2. 基于内存池, 上面的 metaspace, heap 均属于

完 

以上是关于51 java.lang.Class/java.lang.ClassLoader/InstanceKlass/ClassloaderData 的卸载的主要内容,如果未能解决你的问题,请参考以下文章

tensorflow制作自己的数据集

下阴影

51单片机串口通讯

51CTO学院三周年初识51cto到习惯打开51cto

关于keil51单片机头文件的详细解释-51单片机头文件reg51.h详解

美化 url 从 art.php?id=51 到 art/51