前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java热加载机制如何实现

java热加载机制如何实现

作者头像
小土豆Yuki
发布2022-06-24 14:06:50
6870
发布2022-06-24 14:06:50
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗

2022年面试某公司的一道真题,问如何实现热加载,本人当时一脸懵,当时我是这样回答的

  1. 首先热加载是在不重启的应用,使用我们的修改的类
  2. 他肯定是比对有没有修改过,如果有修改过,就会重新加载
  3. 这样就可以了

面试官说,思路是对的,但是具体是怎么实现的呢,我就不会了,今天我们就说一下这道题应该如何解答,我们要从这几方面回答

  1. 双亲委派机制
  2. 自定义类加载器
  3. java热加载实现

双亲委派机制

引导类加载器:负责加载位于JAVA_HOME/lib下的核心类库,如rt.jar包

扩展类加载器:负责加载JAVA_HOME/lib/ext目录下或java.ext.dirs类路径下的所有类库

应用程序加载器:负责加载classpath上的类库

基本思路就是如果一个类加载器收到一个类加载请求,不会自己加载,而是向上进行委托.父类记载不了,再有儿子加载

代码语言:javascript
复制
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();
                    // 在父加载器无法加载时,在调用本身的findClass方法进行类加载,
                   // 最终调用的是URLClassLoader.findClass方法
                   // 真正加载类的逻辑
                    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;
        }
    }

自定义类加载器

loadClass:加载类的入口方法,其中即使遵循双亲委派机制,返回class实例

findClass:如果父类加载器不能加载的时候,由当前类加载findclass进行加载 defineClass:接收字节数组,转成class实例

代码语言:javascript
复制
public class JvmClassLoader1 {    
    public void getValue(){
        System.out.println("JvmClassLoader1");
    }
}

我们写一个自定义类加载器

代码语言:javascript
复制
public class MyClassLoader  extends ClassLoader {

    private  String classPath;

    public MyClassLoader(String classPath){
        this.classPath= classPath;
    }

    private byte[] loadByte(String name) throws IOException {

        String path = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + path.concat(".class"));
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
            return defineClass(name, data, 0, data.length);

        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
}

class MyClassLoaderDemo{

    public static void main(String[] args) throws Exception {
        // 初始化自定义类加载器,会先初始化父类ClassLoader,
        // 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader

        // D盘创建 demo/com/learn/jvm 几级目录,将JvmClassLoader类的复制类JvmClassLoader1.class丢入该目录
        MyClassLoader myClassLoader = new MyClassLoader("D:/demo");
        Class<?> aClass = myClassLoader.loadClass("com.learn.jvm.JvmClassLoader1");
        // 通过反射调用类中方法
        Object object = aClass.newInstance();
        Method getValues = aClass.getDeclaredMethod("getValue", null);
        getValues.invoke(object);
        System.out.println(aClass.getClassLoader().getClass().getName());
    }
}

上面就是一个我们自定义的类加载器,首先我们要明确一个概念 全盘委托:就是使用调用类的类加载器加载当前类 因此上面我们写的类加载器打印出来的类加载器实际上是我们的应用类加载器,而不是我们自定义加载器

打破双亲委派机制

要打破双亲委派机制,我们就要重写loadclass方法

代码语言:javascript
复制
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 t1 = System.nanoTime();

                if(name.contains("com.learn.jvm")){
                    // 自定义加载器加载类
                    c = findClass(name);
                }else{
                    // 父加载器加载
                    c = this.getParent().loadClass(name);
                }

                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

这样我们最终使用的就是我们的自定义类加载器

Java热加载如何实现

此时热加载就很简单了,我们只需要看到类文件修改了,就重新加载一次就可以了,可以使用监听器进行监听文件是否变化,我们这里为了简单,就用一个定时任务去不断加载就可以,如下代码

代码语言:javascript
复制
class MyClassLoaderDemo{


    private static final ScheduledExecutorService scheduledExecutorService = Executors.
            newScheduledThreadPool(1);

    public static void main(String[] args) throws Exception {
        testLoadClass();
    }

    private static void testLoadClass()  {
        // 初始化自定义类加载器,会先初始化父类ClassLoader,
        // 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader

        try {
            // D盘创建 demo/com/learn/jvm 几级目录,将JvmClassLoader类的复制类JvmClassLoader1.class丢入该目录
            MyClassLoader myClassLoader = new MyClassLoader("/Users/wangxuan/Downloads/test/out/production/test");
            Class<?> aClass = myClassLoader.loadClass("com.company.JvmClassLoader1");
            // 通过反射调用类中方法
            Object object = aClass.newInstance();
            Method getValues = aClass.getDeclaredMethod("getValue", null);
            getValues.invoke(object);
            System.out.println(aClass.getClassLoader().getClass().getName());
        } catch (Exception e) {

        }

        scheduledExecutorService.schedule(() -> {
            testLoadClass();
        }, 1 , TimeUnit.SECONDS);
    }

springboot热部署原理

当我们使用编译器启动项目后,在编译器上修改了代码后,编译器会将最新的代码编译成新的.class文件放到classpath下;而引入的spring-boot-devtools插件,插件会监控classpath下的资源,当classpath下的资源改变后,插件会触发重启

而加入了spring-boot-devtools插件依赖后,我们自己编写的文件的类加载器org.springframework.boot.devtools.restart.classloader.RestartClassLoader,是这个工具包自定义的类加载器, 项目依赖的jar使用的是JDK中的类加载器(AppClassLoader\ExtClassLoader\引导类加载器)

在插件触发的重启中,只会使用RestartClassLoader来进行加载(即:只加载我们自己编写的文件部分)

热加载和热部署的区别

  1. 热部署是在服务器中重新部署整个web服务,热加载是重新加载变化的class
  2. 热部署在生产环境,热加载在开发环境
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档