类卸载是指在代码执行过程中,由于某种原因,已加载的类被卸载并从JVM中移除。Java虚拟机规范并没有明确要求JVM自动实现卸载机制,但目前大部分虚拟机都支持类卸载。
实现类卸载的原理是基于类的生命周期。当一个类不再需要时,JVM会从内存中卸载它。在类被卸载之前,JVM需要保证该类不再被引用。如果某个类已经被加载并引用了,在程序中不再引用该类的对象后,JVM会扫描该类的类加载器(ClassLoader)和类加载器所加载的所有类,查找是否还存在该类的实例或引用。如果不存在,该类就可以被卸载。
Java虚拟机规范明确规定了只有以下这些情况,类才能够被卸载:
- 该类所有的实例都已被GC回收并且在所有地方都没有被引用
- 该类的Class对象没有被引用,即这个类的所有对象都已经被销毁或者已经逃逸
- 该类在ClassLoader中不被引用
Java虚拟机在卸载某个类时会先卸载它所依赖的类,因为如果这些类对于当前的应用已经不再需要了,那么这些类对卸载目标类的保留也就没有任何意义了。在卸载一个类之后,Java虚拟机会尝试卸载该类的ClassLoader。
示例代码1:
/**
* 演示类卸载
*/
public class ClassUnloadedDemo {
public static void main(String[] args) throws Exception {
// 开启线程模拟类加载
loadSomeClasses();
// 卸载类
ClassLoader cl = ClassUnloadedDemo.class.getClassLoader();
ClassLoaderUtils.unloadClassLoader(cl);
// 等待线程结束
Thread.sleep(1000L);
}
private static void loadSomeClasses() {
new Thread(() -> {
while (true) {
try {
Class.forName("com.github.simplepooja.Class1", true, ClassLoader.getSystemClassLoader());
Class.forName("com.github.simplepooja.Class2", true, ClassLoader.getSystemClassLoader());
Thread.sleep(100L);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
class ClassLoaderUtils {
private static final Field CLASS_LOADER_FIELD;
private static final Method FIND_LOADED_CLASS_METHOD;
static {
try {
CLASS_LOADER_FIELD = Class.class.getDeclaredField("classLoader");
CLASS_LOADER_FIELD.setAccessible(true);
FIND_LOADED_CLASS_METHOD = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
FIND_LOADED_CLASS_METHOD.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void unloadClassLoader(ClassLoader classLoader) throws Exception {
Thread thread = Thread.currentThread();
ClassLoader originalContextClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(classLoader);
for (Class<?> clazz : getLoadedClasses()) {
if (clazz.getClassLoader() == classLoader) {
unloadClass(clazz);
System.out.println("class unloaded: " + clazz.getName());
}
}
thread.setContextClassLoader(originalContextClassLoader);
}
private static void unloadClass(Class<?> clazz) throws Exception {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
Object obj = field.get(null);
if (obj != null && obj.getClass().getClassLoader() == clazz.getClassLoader()) {
field.set(null, null);
}
}
}
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (!Modifier.isStatic(method.getModifiers())) {
method.setAccessible(true);
try {
SunReflectUtil.invokeMethod(method, null);
} catch (Throwable t) {
}
}
}
Field classLoaderField = CLASS_LOADER_FIELD;
clazz = null;
classLoaderField.set(clazz, null);
}
private static List<Class<?>> getLoadedClasses() throws Exception {
return Arrays.stream(Thread.currentThread().getContextClassLoader().loadClass("java.lang.ClassLoader")
.getDeclaredMethod("getLoadedClasses").invoke(ClassLoader.getSystemClassLoader())).map(obj -> (Class<?>) obj)
.collect(Collectors.toList());
}
}
class Class1 {
int x;
public Class1() {
System.out.println("Class1 constructed!");
}
}
class Class2 {
int x;
public Class2() {
System.out.println("Class2 constructed!");
}
}
上面的代码通过开启线程动态加载Class1和Class2两个类,并在主线程中调用ClassLoaderUtils类的unloadClassLoader方法卸载ClassLoader,以达到卸载Class1和Class2的目的。
示例代码2:
/**
* 验证ClassLoader卸载
*/
public class ClassLoaderUnloadDemo {
public static void main(String[] args) throws Exception {
// 类加载上下文
URL url = new File("target/classes").toURI().toURL();
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
// 加载类
Class<?> clazz = classLoader.loadClass("com.alipay.foo.Foo");
// 创建实例
Object object = clazz.newInstance();
// 打印类加载器信息
System.out.println(clazz.getClassLoader());
// 卸载类加载器
classLoader.close();
// 打印实例信息
System.out.println(object);
}
}
class Foo {
int x;
public Foo() {
System.out.println("Foo constructed!");
}
@Override
public String toString() {
return "Foo instance";
}
}
上面的代码演示了在类加载上下文中加载Foo类,创建实例对象,打印类加载器信息,然后卸载类加载器并再次打印实例信息的过程,可以发现,当类加载器被卸载之后,虽然Foo类的实例仍然存在,但打印实例信息时仅显示"Foo instance",并未打印实例的详细信息,而这正是卸载类加载器的待遇。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:类卸载的实现原理是什么? - Python技术站