文章目录
什么是热加载
热加载是指可以在不重启服务的情况下让更改的代码生效。
热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于***热加载的不安全性,一般不会用于正式的生产环境***。
热加载 VS 热部署
热加载和热部署,都可以在不重启服务的情况下编译/部署项目,都是基于 Java 的类加载器实现的。
部署方式
- 热部署在运行时重新部署整个项目;
- 热加载在运行时重新加载class;
实现原理
- 热部署是在运行时重新部署整个应用,耗时相对较高;
- 热加载是在运行时重新加载class,后台启动一个线程检测类的改变;
使用场景
- 热部署通常在生产环境使用;
- 热加载通常在开发环境使用,线上由于安全性问题不会使用,难以监控。
准备
Java类加载机制
Java类生命周期:加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载,前5个为类加载阶段。
类加载的 5 个阶段中,只有加载阶段是用户可以自定义处理的,而验证阶段、准备阶段、解析阶段、初始化阶段都是 JVM 来处理的。
加载
加载阶段,JVM做3件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
官方定义的类加载器有3个,通过双亲委派机制确定加载顺序:
类加载器 | 加载路径 |
---|---|
BootstrapClassLoader | 处于类加载器层次结构的最高层,负责 sun.boot.class.path 路径下类的加载,默认为 jre/lib 目录下的核心 API 或 -Xbootclasspath 选项指定的 jar 包 |
ExtClassLoader | 加载路径为 java.ext.dirs,默认为 jre/lib/ext 目录或者 -Djava.ext.dirs 指定目录下的 jar 包加载 |
AppClassLoader | 加载路径为 java.class.path,默认为环境变量 CLASSPATH 中设定的值。也可以通过 -classpath 进行指定 |
默认情况下,使用关键字new或者Class.forName都是通过AppClassLoader类加载器来加载的
默认情况下如果要加载一个类,会优先将此类交给其父类进行加载(直到顶层的BootstrapClassLoader),如果父类都无法加载,那么才会将此类交给子类加载。
验证
确保字节码是安全的,确保不会对虚拟机的安全造成危害
准备
确定内存布局,确定内存遍历,赋初始值(注意:是初始值,也有特殊情况)
解析
将符号引用变成直接引用。
初始化
调用程序自定义的代码。规定有且仅有5种情况必须进行初始化:
- new(实例化对象)、getstatic(获取类变量的值,被final修饰的除外,他的值在编译器时放到了常量池)、putstatic(给类变量赋值)、invokestatic(调用静态方法) 时会初始化;
- 调用子类的时候,发现父类还没有初始化,则父类需要立即初始化;
- 虚拟机启动,用户要执行的主类,主类需要立即初始化,如 main 方法;
- 使用 java.lang.reflect包的方法对类进行反射调用方法,会初始化;
- 当使用JDK 1.7的动态语言支持时, 如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、 REF_putStatic、 REF_invokeStatic的方法句柄, 且这个方法句柄所对应的类没有进行过初始化, 则需要先触发其初始化;
如何实现热加载
自定义类加载器
需要自定义类加载器。
为啥需要自定义类加载器
为啥需要自定义类加载器,为什么不能使用AppClassLoader或者ExtClassLoader实现热加载呢?
ClassLoader
源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//确认该类是否已加载,JVM是以 类加载器实例 + 类文件 来表示同一个类
//若这个类被不同类加载器加载,findLoadedClass也还是返回未加载
//所以后面需要每次重新生成类加载器实例
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)