千家信息网

Java中的类动态加载和热替换如何使用

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章主要讲解了"Java中的类动态加载和热替换如何使用",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Java中的类动态加载和热替换如何使用"吧!前
千家信息网最后更新 2026年11月02日Java中的类动态加载和热替换如何使用

这篇文章主要讲解了"Java中的类动态加载和热替换如何使用",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Java中的类动态加载和热替换如何使用"吧!

    前言

    最近,遇到了两个和Java类的加载和卸载相关的问题:

    1) 是一道关于Java的判断题:一个类被首次加载后,会长期留驻JVM,直到JVM退出。这个说法,是不是正确的?

    2) 在开发的一个集成平台中,需要集成类似接口的多种工具,并且工具可能会有新增,同时在不同的环境部署会有裁剪(例如对外提供服务的应用,不能提供特定的采购的工具),如何才能更好地实现?

    针对上面的第2点,我们采用Java插件化开发实现。上面的两个问题,都和Java的类加载和热替换机制有关。

    1. Java的类加载器和双亲委派模型

    1.1 Java类加载器

    类加载器,顾名思义,就是用来实现类的加载操作。每个类加载器都有一个独立的类名称空间,就是说每个由该类加载器加载的类,都在自己的类名称空间,如果要比较两个类是否"相等",首先这两个类必须在相同的类命名空间,即由相同的类加载器加载(即对于任何一个类,都必须由该类本身和加载它的类加载器一起确定其在JVM中的唯一性),不是同一个类加载器加载的类,不会相等。

    在Java中,主要有如下的类加载器:

    下面,简单介绍上面这几种类加载器:

    • 启动类加载器(Bootstrap Class Loader):这个类使用C++开发(所有的类加载器中,唯一使用C++开发的类加载器),用来加载/lib目录中jar和tools.jar或者使用 -Xbootclasspath 参数指定的类。

    • 扩展类加载器(Extension Class Loader):定义为misc.Launcher$ExtClassLoader,用来加载/lib/ext目录或者使用java.ext.dir指定的类。

    • 应用程序类加载器(Application Class Loader):定义为misc.Launcher$AppClassLoader,用来加载用户类路径下面(classpath)下面所有的类,一般情况下,该类是应用程序默认的类加载器。

    • 用户自定义类加载器(User Class Loader):用户自定义类加载器,一般没有必要,后面我们会专门来一部分介绍该类型的类加载器。

    1.2 双亲委派模型

    双亲委派模型,是从 Java1.2 开始引入的一种类加载器模式,在Java中,类的加载操作通过java.lang.ClassLoader中的loadClass()方法完成,咱们首先看看该方法的实现(直接从Java源码中捞出来的):

    protected Class loadClass(String name, boolean resolve)        throws ClassNotFoundException    {        synchronized (getClassLoadingLock(name)) {            // First, check if the class has already been loaded            Class c = findLoadedClass(name);            if (c == null) {                long t0 = System.nanoTime();                try {                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // ClassNotFoundException thrown if class not found                    // from the non-null parent class loader                }                if (c == null) {                    // If still not found, then invoke findClass in order                    // to find the class.                    long t1 = System.nanoTime();                    c = findClass(name);                    // this is the defining class loader; record the stats                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);                    sun.misc.PerfCounter.getFindClasses().increment();                }            }            if (resolve) {                resolveClass(c);            }            return c;        }    }

    我们结合上面的注释,来解释下双亲委派模型的内容:

    1) 接收到一个类加载请求后,首先判断该类是否有加载,如果已经加载,则直接返回;

    2) 如果尚未加载,首先获取父类加载器,如果可以获取父类加载器,则调用父类的loadClass()方法来加载该类,如果无法获取父类加载器,则调用启动器加载器来加载该类;

    3) 判断该类是否被父类加载器或者启动类加载器加载,如果已经加载完成则返回,如果未成功加载,则自己尝试来加载该类。

    上面的描述,说明了loadClass()方法的实现,我们进一步对上面的步骤进行解释:

    • 因为类加载器首先调父类加载器来进行加载,从loadClass()方法的实现,我们知道父类加载器会尝试调自己的父类加载器,直到启动类加载器,所以,任何一个类的加载,都会最终委托到启动类加载器来首先加载;

    • 在前面有进行介绍,启动类加载器、扩展类加载器、应用程序类加载器,都有自己加载的类的范围,例如启动类加载器只加载JDK核心库,因此并不是父类加载器就可以都加载成功,父类加载器无法加载(一般如上面代码,抛出来ClassNotFoundException),此时会由自己加载。

    最后啰嗦一下,再进行一下总结:

    双亲委派模型:如果一个类加载器收到类加载请求,会首先把加载请求委派给父类加载器完成,每个层次的类加载器都是这样,最终所有的加载请求都传动到最根的启动类加载器来完成,如果父类加载器无法完成该加载请求(即自己加载的范围内找不到该类),子类加载器才会尝试自己加载。

    这样的双亲委派模型有个好处:就是所有的类都尽可能由顶层的类加载器加载,保证了加载的类的唯一性,如果每个类都随机由不同的类加载器加载,则类的实现关系无法保证,对于保证Java程序的稳定运行意义重大。

    2. Java的类动态加载和卸载

    2.1 Java类的卸载

    在Java中,每个类都有相应的Class Loader,同样的,每个实例对象也会有相应的类,当满足如下三个条件时,JVM就会卸载这个类:

    1) 该类所有实例对象不可达

    2) 该类的Class对象不可达

    3) 该类的Class Loader不可达

    那么,上面示例对象、Class对象和类的Class Loader直接是什么关系呢?

    在类加载器的内部实现中,用一个Java集合来存放所加载类的引用。而一个Class对象总是会引用它的类加载器,调用Class对象的getClassLoader()方法,就能获得它的类加载器。所以,Class实例和加载它的加载器之间为双向引用关系。

    一个类的实例总是引用代表这个类的Class对象。在Object类中定义了getClass()方法,这个方法返回代表对象所属类的Class对象的引用。此外,所有的Java类都有一个静态属性class,它引用代表这个类的Class对象。

    Java虚拟机自带的类加载器(前面介绍的三种类加载器)在JVM运行过程中,会始终存在,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。因此,由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。

    那么,我们是不是就完全不能在Java程序运行过程中,动态修改我们使用的类了吗?答案是否定的!根据上面的分析,通过Java虚拟机自带的类加载器加载的类无法卸载,我们可以自定义类加载器来加载Java程序,通过自定义类加载器加载的Java类,是可以被卸载的。

    2.2 自定义类加载器

    前面介绍到,类加载的双亲委派模型,是推荐模型,在loadClass中实现的,并不是必须使用的模型。我们可以通过自定义类加载器,直接加载我们需要的Java类,而不委托给父类加载器。

    如上图所示,我们有自定义的类加载器MyClassLoader,用来加载类MyClass,则在JVM中,会存在上面三类引用(上图忽略这三种类型对象对其他的对象的引用)。如果我们将左边的三个引用变量,均设置为null,那么此时,已经加载的MyClass将会被卸载。

    2.3 动态卸载存在的问题

    动态卸载需要借助于JVM的垃圾收集功能才可以做到,但是我们知道,JVM的垃圾回收,只有在堆内存占用比较高的时候,才会触发。即使我们调用了System.gc(),也不会立即执行垃圾回收操作,而只是告诉JVM需要执行垃圾回收,至于什么时候垃圾回收,则要看JVM自己的垃圾回收策略。

    但是我们不需要悲观,即使动态卸载不是那么牢靠,但是实现动态的Java类的热替换还是有希望的。

    3. Java类的热替换

    下面通过代码来介绍Java类的热替换方法(代码简陋,主要为了说明问题):

    如下面的代码:

    首先定义一个自定义类加载器:

    package zmj;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;public class FileClassLoader extends ClassLoader {    private String fileName;    public void setFileName(String fileName) {        this.fileName = fileName;    }    public Class loadClass(String name) throws ClassNotFoundException {        if (name.startsWith("java")) {            return getSystemClassLoader().loadClass(name);        }        Class cls = null;        File classF = new File(fileName);        try {            cls = instantiateClass(name, new FileInputStream(classF), classF.length());        } catch (IOException e) {            e.printStackTrace();        }        return cls;    }    private Class instantiateClass(String name, InputStream fin, long len) throws IOException {        byte[] raw = new byte[(int) len];        fin.read(raw);        fin.close();        return defineClass(name, raw, 0, raw.length);    }}

    上面在loadClass时,先判断类name(包含package的全限定名)是否以java开始,如果是java开始,则使用JVM自带的类加载器加载。

    然后定义一个简单的动态加载类:

    package zmj;public class SayHello {    public void say() {        System.out.println("hello ping...");    }}

    在执行过程中,会动态修改打印内容,测试类的热加载。

    然后定义一个调用类:

    package zmj;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Main {    public static void main(String[] args) throws InterruptedException, ClassNotFoundException,            IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {        while (true) {            FileClassLoader fileClassLoader = new FileClassLoader();            fileClassLoader.setFileName("D:/workspace/idea/test/class-loader-test/target/classes/zmj/SayHello.class");            Object obj = null;            obj = fileClassLoader.loadClass("zmj.SayHello").newInstance();            Method m = obj.getClass().getMethod("say", new Class[]{});            m.invoke(obj, new Object[]{});            Thread.sleep(2000);        }    }}

    当我们运行上面Main程序过程中,我们动态修改执行内容(SayHello中,从 hello zmj... 更改为 hello ping...),最终展示的内容如下:

    hello zmj...

    hello zmj...

    hello zmj...

    hello ping...

    hello ping...

    hello ping...

    感谢各位的阅读,以上就是"Java中的类动态加载和热替换如何使用"的内容了,经过本文的学习后,相信大家对Java中的类动态加载和热替换如何使用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

    对象 动态 方法 模型 委派 双亲 程序 面的 内容 垃圾 问题 两个 代码 实例 就是 过程 应用 开发 运行 代表 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 湖南永感互联网科技有限公司 互联网科技工作委员会副主任毛军 互联网科技开发公司 互联网 科技创业 科技创新互联网企业 奔汉互联网科技 科技伦理 互联网公司 互联网科技生活的内容 湖南洣水互联网科技有限公司扣款 开启互联网科技时代小说 广州慧亚互联网科技怎么样 以后互联网后是啥科技 江阴互联网智能科技产品进口 翼展科技 首家互联网医院 徐州市互联网科技有限公司 中天科技中国工业互联网 东营紫格互联网科技 三亚科技创新互联网基地 现代互联网科技公司 株洲百竞科技的互联网营销顾问 科技互联网产品 深圳科技互联网 互联网科技英文 互联网科技上市公司 互联网科技公司需要的职位 重庆互联网医院沙龙医盟信息科技 荆州市昕露互联网络科技有限公司 互联网公司黑科技创始人 互联网科技数字货币 瑞安市蟠桃会互联网科技有限公司
    0