type
status
date
slug
summary
tags
category
icon
password
很多书和文章,都只提到类何时会被加载,几乎不提到过卸载的情况。那么Java 类可以被卸载吗?
答案是可以的。
类的内存分布
以一个 Sample 类对象创建为例,对应代码为:
其内存占用情况如下:
如果在元数据区保存的类信息过多,就有可能导致元数据区内存超出。
在 JVM 中,通过
-XX:MaxMetaspaceSize
参数控制元数据区内存大小。做个实验,限制
-XX:MaxMetaspaceSize=10m
,不断创建新的类 Class 对象,验证内存溢出的情况。在上面的基础上,增加了一个循环,通过 javassist 包提供的 ClassPool 工具类不断生成新的 Class 对象。同时在每创建出 1000 个类以后,进行一次 GC,试图对 JVM 进行清理。
运行程序,得到下面的结果:
可以看到,在创建到第 3005 个对象以后,出现了 Compressed class space OOM 的错误。
类的卸载
在 JVM 中,存在垃圾回收算法,对不再使用的对象进行清理。
继续回顾类的内存占用分布。如果程序运行过程中,将上图左侧三个引用变量都置为 null,此时 Sample 对象结束生命周期,URLClassLoader 对象结束生命周期,代表 Sample 类的 Class 对象也结束生命周期,Sample 类在方法区内的二进制数据被卸载。
这时,注意到一个问题,系统类加载器是不能回收的,因为类和类加载器是互相引用的关系,而系统类又是我们必须使用到的,因此通过系统类加载器加载的类是不能卸载的。
但是,我们自定义的类加载器是可以卸载的,只需要将栈区的变量全部清除掉就可以了。
因此,做下面的代码进行验证:
可以看到,在触发 GC 前,先将栈中之前的 loader 进行了替换,此时之前的 loader、clazz、obj 已经不再被使用,可以被垃圾回收器处理,因此不会再出现 OOM 的问题。
得到结果如下,此时程序不再出现 OOM 的问题。
补充
尽管自定义类加载器的类可以被卸载,但这是建立在生成实例不再被使用的情况下,并且需要触发垃圾回收才能完成。在开发时,不应该假设类的卸载会定时发生,在复杂系统中,还需考虑借助缓存等功能,重用类加载器。
通过虚拟机参数,可以查看类的加载与卸载过程
- verbose:class 跟踪类的加载和卸载
- XX:+TraceClassLoading 跟踪类的加载
- XX:+TraceClassUnloading 跟踪类的卸载