08热加载
热加载
原理
双亲委派

BootStrapClassLoader:处于类加载器层次结构的最高层,默认负责加载 jre/lib/rt.jar 路径下的核心类,或 -Xbootclasspath 选项指定的jar包;
ExtClassLoader:默认加载路径为 %JAVA_HOME%/lib/ext/*.jar;
AppClassLoader:默认加载路径为环境变量 CLASSPATH 中设定的值。也可以通过 -classpath 选项进行指定;
Custom ClassLoader:可以根据用户的需要定制自己的类加载过程,在运行期进行指定类的动态实时加载; (热替换也是基于该类,来绕过Java类的既定加载过程)
好处
- 对于同一个类加载器实例来说,名字相同的类只能存在一个,并且仅加载一次。不管该类有没有变化,下次再需要加载时,它只是从自己的缓存中直接返回已经加载过的类引用。
- 我们编写的应用类,默认情况下都是通过 AppClassLoader 进行加载的。
ClassLoader主要方法介绍
- findLoadedClass():该方法会在对应加载器的名字空间中寻找指定的类是否已存在,如果存在就返回给类的引用,否则就返回null。
- getSystemClassLoader():该方法返回系统使用的 ClassLoader。可以在自定义的类加载器中通过该方法把一部分工作转交给系统类加载器去处理。
- defineClass():该方法接收以字节数组表示的类字节码,并把它转换成Class实例。该方法转换一个类的同时,会先要求装载该类的父类以及实现的接口类。
- loadClass():加载类的入口方法,调用该方法完成类的显式加载。通过对该方法的重写,可以完全控制和管理类的加载过程。执行loadClass方法,只是单纯的把类加载到内存,并不是对类的主动使用,不会引起类的初始化。
- resolveClass():链接一个指定的类。这是一个在某些情况下确保类可用的必要方法。
ClassLoader.loadClass
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) {
//递归
//如果还有父加载器,调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//如果没有父加载器了,调用BootStrapClassLoader,顶级类加载器
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();
//递归返回中如果发现还是null 就尝试自己加载
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;
}
}类加载原理

- 加载、验证、准备、初始化和卸载5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始。而解析阶段则不一定,它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定。
- 1加载:使用双亲委派
- 2验证:验证是连接阶段的第一步,这个阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 3准备:准备阶段是正式为类变量分配内存并设置类变量(static)初始值的阶段。但不包括静态代码块和实例变量。静态代码块在后面的初始化阶段执行,实例变量将会在对象实例化的时候随着对象一起分到Java堆中的。
- 4解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
- 5初始化: 初始化阶段是执行类构造器方法的过程(注意:不是我们平时说的类的构造方法)。构造器方法是
<cinit>()方法,它是由编译器自动收集类中所有的静态变量的赋值动作和静态代码块中的语句合并产生的
全盘委托
- “全盘委托”是指当一个ClassLoader装载一个类时,除非显示地使用另一个ClassLoader,则该类所依赖及引用的类也由这个ClassLoader载入。
- 例如springboot启动类
代码实现
整体架构

Application.java
package thermal.loading;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import thermal.loading.fileUtil.FileListener;
import thermal.loading.loader.MyClassLoader;
import priv.king.test.Test;
import java.lang.reflect.Method;
/**
* @author king
* TIME: 2020/9/10 - 0:13
**/
public class Application {
public static String projectPath;
public static String rootPath;
public static Class<?> mainClazz;
public void start() throws Exception {
System.out.println("初始化项目");
init();
}
public void init() {
//初始化类
new Test().hello();
}
public static void run(Class<?> clazz) throws Exception {
mainClazz = clazz;
//获取到根目录地址
rootPath = clazz.getResource("/").getPath();
String mainPath = rootPath + clazz.getName().replace(".", "/");
//拼接成主方法包地址
projectPath = mainPath.substring(0, mainPath.lastIndexOf("/"));
System.out.println(projectPath);
MyClassLoader classLoader = new MyClassLoader(rootPath,projectPath);
start0(classLoader);
startFileMino(projectPath);
}
public static void start0(MyClassLoader classLoader) throws Exception {
System.out.println(classLoader);
Class<?> aClass = classLoader.loadClass(Application.class.getName());
Object o = aClass.newInstance();
Method start = aClass.getMethod("start");
start.invoke(o);
}
public static void close() {
System.out.println("关闭项目");
//通知JVM销毁已失去引用的对象(执行)
System.runFinalization();
//通知jvm Gc
System.gc();
}
/**
* 启动文件监听器
*
* @param rootPath
* @throws Exception
*/
public static void startFileMino(String rootPath) throws Exception {
FileAlterationObserver fileAlterationObserver = new FileAlterationObserver(rootPath);
fileAlterationObserver.addListener(new FileListener());
FileAlterationMonitor fileAlterationMonitor = new FileAlterationMonitor(500);
fileAlterationMonitor.addObserver(fileAlterationObserver);
fileAlterationMonitor.start();
}
}
MyClassLoader.java
package thermal.loading.loader;
import thermal.loading.Application;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author king
* TIME: 2020/9/10 - 0:09
**/
public class MyClassLoader extends ClassLoader {
public MyClassLoader(String rootPath, String projectPath) throws Exception {
loadApplication();
LoadClassPath(new File(projectPath), new File(rootPath).getPath());
}
public void loadApplication(){
String path = "/thermal/loading/Application.class";
InputStream resourceAsStream = Application.class.getResourceAsStream(path);
try {
int available = resourceAsStream.available();
byte[] bytes = new byte[available];
resourceAsStream.read(bytes);
defineClass(Application.class.getName(), bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
}
public void LoadClassPath(File file, String rootPath) throws Exception {
if (file.isDirectory()) {
for (File file1 : file.listFiles()) {
LoadClassPath(file1, rootPath);
}
} else {
String fileName = file.getName();
String filePath = file.getPath();
String endName = fileName.substring(fileName.lastIndexOf(".") + 1);
if (endName.equals("class")) {
InputStream inputStream = new FileInputStream(file);
byte[] bytes = new byte[(int) file.length()];
inputStream.read(bytes);
String className = filePathToClassName(filePath, rootPath);
//重新加载class
defineClass(className, bytes, 0, bytes.length);
}
}
}
public String filePathToClassName(String filePath, String rootPath) {
String className = filePath.replace(rootPath, "").replace("\\", ".");
//去掉后缀 去掉第一个斜杠
className = className.substring(0, className.lastIndexOf(".")).substring(1);
return className;
}
}FileListener.java
package thermal.loading.fileUtil;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import thermal.loading.Application;
import thermal.loading.loader.MyClassLoader;
import java.io.File;
/**
* @author king
* TIME: 2020/9/10 - 0:12
**/
public class FileListener extends FileAlterationListenerAdaptor {
@Override
public void onFileChange(File file) {
if (file.getName().indexOf(".class") != -1) {
try {
Application.close();
MyClassLoader myClassLoader = new MyClassLoader(Application.rootPath, Application.projectPath);
Application.start0(myClassLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}Main.java
package priv.king;
import thermal.loading.Application;
/**
* @author king
* TIME: 2020/9/10 - 0:17
**/
public class Main {
public static void main(String[] args) throws Exception{
Application.run(Main.class);
}
}Test.java
package priv.king.test;
/**
* @author king
* TIME: 2020/9/10 - 0:14
**/
public class Test {
public void hello(){
System.out.println("Test class version 3v");
}
}08热加载
https://jiajun.xyz/2020/10/10/java/java基础/08热加载/