《深入理解 Java 虚拟机》阅读笔记2系列:附录:实现Java类的热替换

本文探讨了Java类的热替换技术,详细解释了热替换的实现原理,即利用不同的类加载器加载类的不同版本,以避开双亲委派模型。通过一个简单的例子展示了如何在运行时替换Foo类,使得控制台输出内容改变,从而验证热替换的成功。
摘要由CSDN通过智能技术生成

实现 Java 类的热替换

什么是热替换及其实现原理

  • 热替换是在不停止正在运行的系统的情况下进行类(对象)的升级替换;
  • 这要求虚拟机中要存在同一个类的两个不同版本。可我们知道,我们是无法将同一个类加载两遍的,想要实现这点,我们需要让虚拟机认为这是两个不同的类,即用两个不同的类加载器去加载这个类不同版本的 class 文件;
  • 因此,这个工作就不能由系统提供给我们的启动类加载器,扩展类加载器或者应用程序类加载器来完成,因为这三个类加载器在同一个虚拟机中只有一份,不仅如此,我们还要跳过这些类加载器;
  • 想要跳过这些类加载器可不是只要不用这些类加载器就行了,还需要我们跳过双亲委派模型,否则类的加载还会被委派到这些个类加载器,如果恰好某个类之前是由这三个类加载器中的一个加载的,虚拟机就不会再次加载新版本的类了,就无法实现类的热替换了。

实现简单的 Java 类热替换

需求分析

现有一 Foo 类,可以在控制台持续打印:Hello world! version one,我们将在该类运行时,将其 .class 文件替换为修改后的 Foo 类的 .class 文件,修改后的 Foo 会在控制台持续打印:Hello world! version two。也就是说,替换之后,控制台打印的内容发生变化,就说明类的热替换实现成功。

Foo 类的实现:

public class Foo {
    public void sayHello() {
        System.out.println("Hello world! version one");
        // System.out.println("Hello world! version two");  // 之后替换成这个
    }
}

然后我们通过如下程序运行 Foo 类:

public class Task extends TimerTask {
    @Override
    public void run() {
        String basePath = "C:\\Users\\Bean\\IdeaProjects\\USTJ\\target\\classes";
        // 每执行一次任务都 new 一个新的类加载器
        HotswapClassLoader cl = new HotswapClassLoader(
            basePath, new String[]{"com.jvm.ch7.hotswap.Foo"});
        try {
            // 通过我们自己实现的类加载器加载 Foo 类
            Class cls = cl.loadClass("com.jvm.ch7.hotswap.Foo", true); 
            Object foo = cls.newInstance();
            Method method = cls.getMethod("sayHello", new Class[]{});
            method.invoke(foo, new Object[]{});
        } catch (Exception e) {
        	e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new Task(), 0, 1000);
    }
}

实现类加载器

HotswapClassLoader 的实现如下,具体的讲解已被写入注释中:

public class HotswapClassLoader extends ClassLoader {
    private String basePath;
    private HashSet<String> loadedClass;  // 用来记录被这个类加载器所加载的类

    public HotswapClassLoader(String basePath, String[] classList) {
        // 跳过父类加载器,把它设为null
        super(null);
        this.basePath = basePath;
        loadedClass = new HashSet<>();
        // 该类加载器在初始化的时候会直接把应该它负责加载的类加载好,
        // 这样之后 loadClass 时,会在第一步检验该类是否已经被加载时发现该类已经被加载过了,
        // 就无需执行 loadClass 之后的流程,直接返回虚拟机中被加载好的类即可,
        // 这样虽然初始化的时间长了点,但是之后 loadClass 时会比较省时间
        loadClassByMe(classList);
    }

    /**
     * 加载给定的的 classList 中的类到虚拟机
     */
    private void loadClassByMe(String[] classList) {
        for (int i = 0; i < classList.length; i++) {
            Class cls = loadClassDirectly(classList[i]);
            if (cls != null) {
                loadedClass.add(classList[i]);
            }
        }
    }

    /**
     * 通过文件名直接加载类,得到Class对象
     */
    private Class loadClassDirectly(String className) {
        Class cls = null;
        StringBuilder sb = new StringBuilder(basePath);
        String classPath = className.replace(".", File.separator) + ".class";
        sb.append(File.separator + classPath);
        File file = new File(sb.toString());
        InputStream fin = null;
        try {
            fin = new FileInputStream(file);
            // 将字节流转化成内存中的Class对象
            cls = instantiateClass(className, fin, (int) file.length());
            return cls;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * 将字节流转化成内存中的Class对象啊,使用defineClass方法!
     */
    private Class instantiateClass(String name, InputStream fin, int len) {
        byte[] buffer = new byte[len];
        try {
            fin.read(buffer);
            return defineClass(name, buffer, 0, len);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * 覆盖原有的loadClass规则,
     */
    public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class cls = null;
        // 应该由 HotswapClassLoader 负责加载的类会通过下面这一行得到类的 Class 对象,
        // 因为早在 HotswapClassLoader 类加载器执行构造函数时,它们就被加载好了
        cls = findLoadedClass(name);
        // 只有在这个类没有被加载,且!这个类不是当前这个类加载器负责加载的时候,才去使用启动类加载器
        if (cls == null && !loadedClass.contains(name)) {
            cls = findSystemClass(name);
        }
        if (cls == null) {
            throw new ClassNotFoundException(name);
        }
        // resolveClass是进行连接操作的,即"验证+准备+解析",之后就可以进行初始化了
        if (resolve) {
            resolveClass(cls);
        }
        return cls;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值