类卸载的实现原理是什么?

yizhihongxing

类卸载是指在代码执行过程中,由于某种原因,已加载的类被卸载并从JVM中移除。Java虚拟机规范并没有明确要求JVM自动实现卸载机制,但目前大部分虚拟机都支持类卸载。

实现类卸载的原理是基于类的生命周期。当一个类不再需要时,JVM会从内存中卸载它。在类被卸载之前,JVM需要保证该类不再被引用。如果某个类已经被加载并引用了,在程序中不再引用该类的对象后,JVM会扫描该类的类加载器(ClassLoader)和类加载器所加载的所有类,查找是否还存在该类的实例或引用。如果不存在,该类就可以被卸载。

Java虚拟机规范明确规定了只有以下这些情况,类才能够被卸载:

  1. 该类所有的实例都已被GC回收并且在所有地方都没有被引用
  2. 该类的Class对象没有被引用,即这个类的所有对象都已经被销毁或者已经逃逸
  3. 该类在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技术站

(0)
上一篇 2023年5月11日
下一篇 2023年5月11日

相关文章

  • Java面试题冲刺第二十一天–JVM

    Java面试题冲刺第二十一天–JVM 一、了解JVM 1. JVM的概念 JVM(Java Virtual Machine)即Java虚拟机,是Java语言的运行环境,负责将Java字节码文件转换为机器指令执行。 2. JVM的内部结构 JVM的内部结构分为三个部分:类加载器,运行时数据区,执行引擎。 2.1 类加载器 用来加载类文件,包括如下几种类型: …

    Java 2023年5月26日
    00
  • 详解SpringBoot配置连接池

    Spring Boot是一个快速创建Web应用程序的框架,它提供了许多便捷的功能和工具,其中包括连接池。连接池是一种管理数据库连接的技术,它可以提高应用程序的性能和可伸缩性。下面是详解Spring Boot配置连接池的完整攻略: 添加依赖项 首先,我们需要在pom.xml文件中添加连接池依赖项。Spring Boot支持多种连接池,包括HikariCP、To…

    Java 2023年5月14日
    00
  • java:程序包org.apache.ibatis.annotations不存在报错解决

    如果在使用MyBatis时出现“java:程序包org.apache.ibatis.annotations不存在”的报错,原因可能是缺乏MyBatis-annotations的依赖或版本不匹配。为了解决这个问题,可以按照以下步骤进行操作: 步骤一、添加MyBatis-annotations依赖 打开项目的pom.xml文件,查看是否添加了MyBatis-an…

    Java 2023年5月19日
    00
  • Java利用Netty时间轮实现延时任务

    Java利用Netty时间轮实现延时任务 Netty是一个高性能、异步事件驱动的网络应用程序框架,常用于网络编程、RPC等高并发场景。Netty提供了对时间轮数据结构的支持,我们可以基于时间轮实现延时任务功能,本文将详细介绍如何利用Netty时间轮实现延时任务。 时间轮数据结构 时间轮是一种定时器管理方式,将所有的定时器事件按照时间分配到不同的槽中,形成一个…

    Java 2023年5月20日
    00
  • Mybatis三种批量插入数据的方式

    Sure! 首先,我们先了解一下 Mybatis 中三种批量插入数据的方式: 1.基于 statement 的方式2.基于 batch 的方式3.基于 foreach 标签的方式 下面我将详细讲解这三种方式的过程和示例: 基于 statement 的方式 创建一个包含多个 insert 语句的 sql 文件,例如 insert_test.sql 文件如下: …

    Java 2023年5月20日
    00
  • JavaSpringBoot报错“NotFoundException”的原因和处理方法

    原因 “Not Found Exception” 错误通常是以下原因引起的: 路径错误:如果您的路径存在问题,则可能会出现此错误。在这种情况下,需要检查您的路径并确保它们正确。 数据库查询问题:如果您的数据库查询存在问题,则可能会出现此错误。在这种情况下,需要检查您的数据库查询并确保它们正确。 代码逻辑问题:如果您的代码逻辑存在问题,则可能会出现此错误。在这…

    Java 2023年5月4日
    00
  • SpringBoot控制配置类加载顺序方式

    SpringBoot是一个基于Spring框架的开源应用程序开发框架,主要用于快速构建基于Spring的企业级应用程序。而SpringBoot中一个非常重要的机制就是使用控制配置类进行应用程序的配置。控制配置类可以通过多种方式进行加载,这里我们就详细讲解一下SpringBoot控制配置类加载顺序方式以及相应实例。 控制配置类的加载顺序方式 SpringBoo…

    Java 2023年5月31日
    00
  • Spring Security组件一键接入验证码登录和小程序登录的详细过程

    讲解Spring Security组件一键接入验证码登录和小程序登录的步骤如下: 1. 导入Spring Security组件 在Spring Boot项目中,我们可以很方便地通过引入依赖的方式来导入Spring Security组件。在pom.xml文件中,添加以下依赖: <dependency> <groupId>org.spri…

    Java 2023年6月3日
    00
合作推广
合作推广
分享本页
返回顶部