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

类卸载是指在代码执行过程中,由于某种原因,已加载的类被卸载并从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实现往字符串中某位置加入一个字符串

    现在我会详细讲解如何在Java中实现往字符串中某位置加入一个字符串的完整攻略。下面是具体步骤: 1. 创建一个StringBuilder对象 在Java中,比起直接使用String类,我们更推荐使用StringBuilder类来进行字符串拼接。因为每次对一个String对象进行字符串拼接时,都会创建一个新的String对象,这样就会浪费很多内存空间。 Str…

    Java 2023年5月26日
    00
  • Java编程生产者消费者实现的四种方法

    Java编程生产者消费者实现的四种方法 生产者消费者问题是指在生产者和消费者之间同步的问题。生产者一直在生产消息,消费者一直在从队列中取走消息,并且队列中只能存储有限的消息。Java中提供了多种实现生产者消费者问题的方法,具体如下: 方法一:使用wait()和notify()方法 这是最基本的一种实现方式。使用wait()方法让生产者线程等待,当消息队列满时…

    Java 2023年5月18日
    00
  • ant使用指南详细入门教程

    Ant使用指南详细入门教程 Ant 是一款 Java 应用程序构建工具,它采用基于 XML 的构建脚本描述文件,可以使用 Ant 提供的任务集来编译、打包、测试、部署等一系列构建工作。本篇文章将从 Ant 的安装和配置开始讲解,到 Ant 的常用任务和实例演示,带领读者全面入门 Ant 构建工具。 安装和配置 Ant 安装 Java 运行时环境 Ant 是基…

    Java 2023年6月15日
    00
  • idea使用外置tomcat配置springboot详细步骤

    下面是我为你准备的“idea使用外置tomcat配置springboot详细步骤”的攻略。希望能对你有所帮助。 1. 确定工具版本 在开始这个过程之前,我们需要确定使用的工具版本,以确保配置的正确性。以下是我们使用的工具版本: IDE: IntelliJ IDEA 2020.2 Tomcat: Apache Tomcat 9.0.38 Spring Boot…

    Java 2023年5月19日
    00
  • Java:如何加密或解密PDF文档?

    在工作中,我们会将重要的文档进行加密,并且设置用户的访问权限,其他外部人员均无法打开,只有获取该权限的用户才有资格打开文档。此外,限制用户的使用权限,极大程度上阻止了那些有意要篡改、拷贝其中内容的人,提高文档的安全性。与此同时,文档加密的另一大作用是为了防止丢失,因为可能存在员工出差或离职时,将文档有意或无意的删除,造成文档丢失的现象,从而导致公司的业务和形…

    Java 2023年4月18日
    00
  • java基础之 Arrays.toString()方法详解

    Java基础之Arrays.toString()方法详解 概述 在Java中,Arrays.toString()方法可以将一个数组转换成字符串的形式。这个方法非常方便,可以用于快速打印出数组的内容,也可以用于输出数组的值到日志文件中。 语法 数组转换成字符串的语法如下: public static String toString(Object[] a) 方法…

    Java 2023年5月26日
    00
  • asp.net开发微信公众平台之获取用户消息并处理

    我非常愿意为您讲解“asp.net开发微信公众平台之获取用户消息并处理”的完整攻略。 前置条件 在进行下面的步骤之前,您需要准备好以下前置条件: 一个搭建好的asp.net项目。 一个微信公众号。 在微信公众平台上获取到公众号的AppID和AppSecret。 安装WeChat SDK。 步骤1:获取微信服务器发送的消息 通过ASP.NET处理微信公众平台的…

    Java 2023年5月19日
    00
  • springboot登陆页面图片验证码简单的web项目实现

    下面我来详细讲解“springboot登陆页面图片验证码简单的web项目实现”的完整攻略。 简介 本项目是一个基于Spring Boot框架的简单web项目,使用图片验证码来保护用户登录页面,防范恶意攻击和爆破。 实现步骤 第一步:新建Spring Boot项目 首先,我们需要新建一个Spring Boot项目,以便进行后续的开发。在创建项目时需要注意选择W…

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