类的热替换,是指程序在运行的时候,对内存方法区中类定义进行替换。因为堆中的 Class 对象是对方法区对象的封装,所以可以理解为对Class对象的替换,当一个Class被替换后,系统无需重启,替换的类会立即生效。
说明:在类的加载过程中,类的结构信息会存在在JVM的方法区中,类的具体对象会在堆中分配内存空间。
可以看另一篇博文: Java虚拟机的类加载机制
二、类的加载在Java中,类的实例化分为两部分:类的加载和类的实例化。而类的加载又分为显式加载和隐式加载。
我们平时使用new创建类实例时,其实就是隐式地包含了类的加载过程。对于类的显式加载,比较常用的是用 Class.forName()
方法。其实,它们都是通过调用 ClassLoader
类的 loadClass()
方法来完成类的实际加载工作。直接调用 ClassLoader
的loadClass()
方法是另外一种不常用的显式加载类的方法。
下面来介绍一下ClassLoader类:
- ClassLoader 是一个抽象类
- ClassLoader 的实例把读入的Java字节码类装载到JVM中
- ClassLoader 可以定制,满足不同的字节码流获取方式
- ClassLoader 负责类装载过程中的加载阶段
ClassLoader在加载类时有一定的层次关系和规则。在Java中,有四种类型的类加载器,分别为:
- BootStrapClassLoader(启动ClassLoader)
- ExtClassLoader(扩展ClassLoader)
- AppClassLoader(应用ClassLoader)
- Custom ClassLoader(自定义ClassLoader)
这四种类加载器分别负责不同路径的类的加载,并形成了一个类加载的层次结构。见下图
说明:
-
BootStrapClassLoader:处于类加载器层次结构的最高层,默认负责加载 jre/lib/rt.jar 路径下的核心类,或 -Xbootclasspath 选项指定的jar包
-
ExtClassLoader:默认加载路径为 %JAVA_HOME%/lib/ext/*.jar
-
AppClassLoader:默认加载路径为环境变量 CLASSPATH 中设定的值。也可以通过 -classpath 选项进行指定
-
Custom ClassLoader:可以根据用户的需要定制自己的类加载过程,在运行期进行指定类的动态实时加载;
热替换也是基于该类,来绕过Java类的既定加载过程
一般来说,这四种类加载器会形成一种父子关系,高层为低层的父加载器。在类进行加载时,首先会自底向上挨个检查是否已经加载了指定类,如果已经加载,则直接返回该类的引用。如果到最高层也没有找到加载过指定类,那么会自顶向下挨个尝试加载,直到用户自定义类加载器,如果还不能成功,就会抛出异常。过程如下图:
每个类加载器有自己的名字空间,对于同一个类加载器实例来说,名字相同的类只能存在一个,并且仅加载一次。不管该类有没有变化,下次再需要加载时,它只是从自己的缓存中直接返回已经加载过的类引用。
我们编写的应用类,默认情况下都是通过 AppClassLoader 进行加载的。当我们使用 new 关键字或使用 Class.forName() 来加载类时,所要加载的类都是由调用 new(Class.forName)类的类加载器(也是AppClassLoader)进行加载的。
要想实现Java类的热替换,首先必须要让系统中同名类的不同版本实例的共存,要想实现同一个类的不同版本的共存,必须要通过不同的类加载器来加载该类的不同版本。另外,为了能够绕过Java类的既定加载过程,需要实现自己的类加载器。
四、自定义类加载器CustomLoader为什么要自定义类加载器?
- 隔离加载类
- 修改类加载的方式
- 扩展加载源
- 防止源码泄漏
为了能够完全掌控类的加载过程,需要自定义类加载器,且需要从ClassLoader继承。下面来介绍一下ClassLoader类中和热替换有关的一些重要方法。
findLoadedClass()
该方法会在对应加载器的名字空间中寻找指定的类是否已存在,如果存在就返回给类的引用,否则就返回null。每个类加载器都维护有自己的一份已加载类名字空间,其中不能出现两个同名的类。凡是通过该类加载器加载的类,无论是直接的还是间接的,都保存在自己的名字空间中,这里的直接是指,存在于该类加载器的加载路径上并由该加载器完成加载,间接是指,由该类加载器把类的加载工作委托给其他类加载器完成类的实际加载。
getSystemClassLoader()
该方法返回系统使用的 ClassLoader。可以在自定义的类加载器中通过该方法把一部分工作转交给系统类加载器去处理。
defineClass()
该方法接收以字节数组表示的类字节码,并把它转换成Class实例。该方法转换一个类的同时,会先要求装载该类的父类以及实现的接口类。
loadClass()
加载类的入口方法,调用该方法完成类的显式加载。通过对该方法的重写,可以完全控制和管理类的加载过程。执行loadClass方法,只是单纯的把类加载到内存,并不是对类的主动使用,不会引起类的初始化。
resolveClass()
链接一个指定的类。这是一个在某些情况下确保类可用的必要方法。
2、自定义加载器了解了上面的这些方法,接下来实现一个自定义的类加载器来实现热替换,在给出示例代码前,再重申两点内容:
-
要想实现同一个类的不同版本的共存,那么这些不同版本必须由不同的类加载器进行加载,因此就不能把这些类的加载工作委托给系统加载器来完成,因为它们只有一份。
-
为了做到这一点,就不能采用系统默认的类加载器委托规则,也就是说我们定制的类加载器的父加载器必须设置为null。
该定制的类加载器的实现代码如下:
public class CustomClassLoader extends ClassLoader {
private String basedir; // 需要该类加载器直接加载的类文件的基目录
private HashSet className; // 需要由该类加载器直接加载的类名
public CustomClassLoader(String basedir, String[] clazns) throws Exception {
super(null); // 指定父类加载器为 null
this.basedir = basedir;
className = new HashSet();
loadClassByMe(clazns);
}
private void loadClassByMe(String[] clazns) throws Exception {
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?