java自定义类加载器并观察类卸载过程

欢迎关注本人公众号

在这里插入图片描述

概述

之前我写了篇博客: java自定义类加载器实现类隔离 ,里面介绍了如何自定义类加载器实现了类隔离。

通常情况下,在JSP,OSGI及其他一些支持热替换的库,都是需要进行类的卸载回收的,否则类在替换后,老的类就没用了但是还在内存中,就会造成内存泄漏。

我们知道类的卸载需要满足以下三个条件:

  • 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
  • 加载该类的ClassLoader已经被GC。
  • 该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。

所以在自定义类加载器时,就要注意这一点,如果你是希望其使用完成后就被卸载,那么就需要特别留意类加载器及类的作用域了。

代码实现

我这里还是在 java自定义类加载器实现类隔离 的基础上,对代码进行一些调整,使其可以在使用完成后可以被卸载。注意这里的代码只是学习使用的实例,如果用于生产环境还需要慎重。

在D盘的a,b两个目录下准备Hello.class 和 Dog.class 两个文件还是使用 java自定义类加载器实现类隔离 提到的方法来实现。

接下来对通过URLClassLoader实现的test0方法进行改造:

@Test
public void test0() throws Exception {
    test4();

    System.gc();

    TimeUnit.SECONDS.sleep(5);
}

public void test4() throws Exception {
    System.out.println(this.getClass().getClassLoader());

    URLClassLoader diskLoader = new URLClassLoader(new URL[]{new URL("file:/D:/liubenlong/a/")});//最后面的斜杠需要添加
    URLClassLoader diskLoader1 = new URLClassLoader(new URL[]{new URL("file:/D:/liubenlong/b/")});

    //加载class文件
    Class clz = diskLoader.loadClass("Hello");
    Constructor constructor = clz.getConstructor(String.class);
    Object obj = constructor.newInstance("tom");

    /**
     * 类Hello引用了类Dog,类加载器会主动加载被引用的类。
     * 注意一般是我们使用 URLClassLoader 实现自定义的类加载器。如果使用classLoader,则需要重写findClass方法来实现类字节码的加载
     */
    Method method = clz.getMethod("sayHello", null);
    //通过反射调用Test类的say方法
    method.invoke(obj, null);

    Class clz1 = diskLoader1.loadClass("Hello");
    Constructor constructor1 = clz1.getConstructor(String.class);
    Object obj1 = constructor1.newInstance("cat");

    Method method1 = clz1.getMethod("sayHello", null);
    //通过反射调用Test类的say方法
    method1.invoke(obj1, null);
}

这里System.gc();是为了主动触发GC进行类卸载。后面的sleep只是为了等待程序执行完成,输出结果。

然后需要添加启动参数-verbose:class来打印出类加载及类卸载的日志信息。

运行test0,输出

//省略部分日志
[0.482s][info][class,load] Hello source: file:/D:/liubenlong/a/
[0.483s][info][class,load] Dog source: file:/D:/liubenlong/a/
[0.484s][info][class,load] java.lang.invoke.StringConcatFactory source: jrt:/java.base
//省略部分日志
[0.508s][info][class,load] java.io.DataInputStream source: jrt:/java.base
a hi ...  java.net.URLClassLoader@3891771e
//省略部分日志
[0.549s][info][class,load] java.lang.invoke.LambdaForm$MH/0x0000000100101c40 source: java.lang.invoke.LambdaForm
a hello tom  java.net.URLClassLoader@3891771e
[0.552s][info][class,load] Hello source: file:/D:/liubenlong/b/
[0.556s][info][class,load] Dog source: file:/D:/liubenlong/b/
b hi ...  java.net.URLClassLoader@78ac1102
b hello cat  java.net.URLClassLoader@78ac1102
[0.560s][info][class,unload] unloading class Dog 0x0000000100102258
[0.560s][info][class,unload] unloading class Hello 0x0000000100102040
[0.560s][info][class,unload] unloading class Dog 0x00000001000a1a58
[0.560s][info][class,unload] unloading class Hello 0x00000001000a1840
[5.581s][info][class,load  ] java.lang.Shutdown source: jrt:/java.base
[5.581s][info][class,load  ] java.lang.Shutdown$Lock source: jrt:/java.base

Process finished with exit code 0

日志中可以看出class load 和 unload 的信息,以及Hello和Dog类的类加载器。

注意,我这里时是加了一个test4方法来进行操作类及处理业务逻辑,test0调用test4方法并且调用System.gc();。如果System.gc();直接写道test4方法的末尾,是无法实现类的卸载的。读者可以自己实验。

原因是如果放在一起的话,都是同一个方法内,方法是虚拟机执行的最小单元,调用test4方法时会生成一个栈帧放到栈顶。test4方法的局部变量就会存在与栈帧中的局部变量表中,这里就有URLClassLoader类加载器及Hello/Dog类实例的引用,还包括一些动态链接,所以在GC时,由于栈帧中的内容是作为GC ROOT的,所以肯定不会被回收,故而不会进行类的卸载。
注意在实际开发中一定要保证类的实例, 该类的ClassLoader都被回收,并且没该类不可以被反射调用,才可以类卸载。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐崇拜234

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值