1. 什么是热加载

热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环境。

2. 热加载与热部署的区别

首先,不管是热加载还是热部署,都可以在不重启服务的情况下编译/部署项目,都是基于 Java 的类加载器实现的。

那么两者到底有什么区别呢?

在部署方式上:

  • 热部署是在服务器运行时重新部署项目。
  • 热加载是在运行时重新加载 class

在实现原理上:

  • 热部署是直接重新加载整个应用,耗时相对较高。
  • 热加载是在运行时重新加载 class,后台会启动一个线程不断检测你的类是否改变。

在使用场景上:

  • 热部署更多的是在生产环境使用。
  • 热加载则更多的是在开发环境上使用。线上由于安全性问题不会使用,难以监控。

3. 类加载五个阶段


热加载 java 热加载是什么_虚拟机上部署的项目 访问路径怎么写


可能你已经发现了,图中一共是7个阶段,而不是5个。是因为图是类的完整生命周期,如果要说只是类加载阶段的话,图里最后的使用(Using)和卸载(Unloading)并不算在内。

简单描述一下类加载的五个阶段:

  1. 加载阶段:找到类的静态存储结构,加载到虚拟机,定义数据结构。用户可以自定义类加载器。
  2. 验证阶段:确保字节码是安全的,确保不会对虚拟机的安全造成危害。
  3. 准备阶段:确定内存布局,确定内存遍历,赋初始值(注意:是初始值,也有特殊情况)。
  4. 解析阶段: 将符号变成直接引用。
  5. 初始化阶段:调用程序自定义的代码。规定有且仅有5种情况必须进行初始化。
  6. new(实例化对象)、getstatic(获取类变量的值,被final修饰的除外,他的值在编译器时放到了常量池)、putstatic(给类变量赋值)、invokestatic(调用静态方法) 时会初始化
  7. 调用子类的时候,发现父类还没有初始化,则父类需要立即初始化。
  8. 虚拟机启动,用户要执行的主类,主类需要立即初始化,如 main 方法。
  9. 使用 java.lang.reflect包的方法对类进行反射调用方法 是会初始化。
  10. 当使用JDK 1.7的动态语言支持时, 如果一个java.lang.invoke.MethodHandle实例最后
  11. 的解析结果REF_getStatic、 REF_putStatic、 REF_invokeStatic的方法句柄, 并且这个方法句柄
  12. 所对应的类没有进行过初始化, 则需要先触发其初始化。

要说明的是,类加载的 5 个阶段中,只有加载阶段是用户可以自定义处理的,而验证阶段、准备阶段、解析阶段、初始化阶段都是用 JVM 来处理的。

4. 实现类的热加载

4.1 实现思路

我们怎么才能手动写一个类的热加载呢?根据上面的分析,Java 程序在运行的时候,首先会把 class 类文件加载到 JVM 中,而类的加载过程又有五个阶段,五个阶段中只有加载阶段用户可以进行自定义处理,所以我们如果能在程序代码更改且重新编译后,让运行的进程可以实时获取到新编译后的 class 文件,然后重新进行加载的话,那么理论上就可以实现一个简单的 Java 热加载

所以我们可以得出实现思路:

  1. 实现自己的类加载器。
  2. 从自己的类加载器中加载要热加载的类。
  3. 不断轮训要热加载的类 class 文件是否有更新。
  4. 如果有更新,重新加载。

4.2 自定义类加载器

设计 Java 虚拟机的团队把类的加载阶段放到的 JVM 的外部实现( 通过一个类的全限定名来获取描述此类的二进制字节流 )。这样就可以让程序自己决定如果获取到类信息。而实现这个加载动作的代码模块,我们就称之为 “类加载器”。

在 Java 中,类加载器也就是 java.lang.ClassLoader. 所以如果我们想要自己实现一个类加载器,就需要继承 ClassLoader 然后重写里面 findClass的方法,同时因为类加载器是 双亲委派模型实现(也就说。除了一个最顶层的类加载器之外,每个类加载器都要有父加载器,而加载时,会先询问父加载器能否加载,如果父加载器不能加载,则会自己尝试加载)所以我们还需要指定父加载器。

最后根据传入的类路径,加载类的代码看下面。

package net.codingme.box.classloader;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;/** *

* 自定义 Java类加载器来实现Java 类的热加载 * * @Author niujinpeng * @Date 2019/10/24 23:22 */public class MyClasslLoader extends ClassLoader { /** 要加载的 Java 类的 classpath 路径 */ private String classpath; public MyClasslLoader(String classpath) { // 指定父加载器 super(ClassLoader.getSystemClassLoader()); this.classpath = classpath; } @Override protected Class> findClass(String name) throws ClassNotFoundException { byte[] data = this.loadClassData(name); return this.defineClass(name, data, 0, data.length); } /** * 加载 class 文件中的内容 * * @param name * @return */ private byte[] loadClassData(String name) { try { // 传进来是带包名的 name = name.replace(".