一、什么是热加载
简单来说,就是不重启一个项目,使得部分代码更新,原理是通过java类加载器实现的。由于类的加载缺少监控使得安全性不能得到保证,一般使用在开发环境,加快开发效率。俗称开发者模式。实现自定义加载器前,先讲一讲JVM的类加载器。
二、JVM的加载器
1、BootStrap ClassLoader
BootStrap ClassLoader又叫启动类加载器,是最顶级的父类加载器,加载最核心的类,,如java.lang.、java.uti.等; 这些类位于$JAVA_HOME/jre/lib/rt.jar;
2、Extension ClassLoader
Extension ClassLoader 又叫扩展类加载器,负责加载%JAVA_HOME%/jre/lib/ext/目录下的扩展包
3、Application ClassLoader
Application ClassLoader又叫应用类加载器,负责加载classpath也就是环境变量下的类
类加载机制
工作原理:类加载采用双亲委派机制,简单来说,一个应用被加载时首先判断是否被加载过,若无则进行加载,并判断是否有父类,若有则要求父类进行加载,到顶级父类时再根据包的位置进行加载,不满足则要求子类加载。
好处:可以避免重复加载,也可以保证类加载的安全性,若自己写了个String包,那是否会替换jdk包中的String类呢?显然不可能,因为使用了这种机制来避免这种问题。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先判断该类是否已经被加载过
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) {
//自定义加载,findClass是个空方法就是让我们重写的
long t1 = System.nanoTime();
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;
}
}
JVM是如何判断一个类是否被加载过的呢?通过findLoadedClass(name),即 java虚拟机已将此加载器记录为具有给定二进制名称的某个类的启动加载器,则返回该二进制名称的类。否则,返回null。
三、自定义加载器
首先新建一个类并继承ClassLoader。并重写findClass()方法。注释已写方法用处。
public class MyClassLoader extends ClassLoader {
@Override
//通过名称找到具体class文件,并进行加载
protected Class<?> findClass(String name) {
byte[] b = loadClassData(name);
name = name.split("\\.")[1];
//然后通过defineClass()将二进制文件转化为Class对象
return super.defineClass(name, b, 0, b.length);
}
//这个方法就是将具体位置的class文件转化为二进制流
private byte[] loadClassData(String name) {
try {
name = name.replace(".", "\\");
FileInputStream fileInputStream = new FileInputStream(new File("D:\\IntelliJ IDEA 2019.1\\JavaHotFix\\" + name+".class"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i = 0;
while ((i = fileInputStream.read()) != -1) {
byteArrayOutputStream.write(i);
}
fileInputStream.close();
return byteArrayOutputStream.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
随便定义一个对象并定义一个方法
public class People {
public void hello() {
System.out.println("我是People");
}
}
进行测试
public class Main {
public static void main(String[] args) {
while (true) {
try {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> clazz = myClassLoader.loadClass("src.People");
Object people = clazz.newInstance();
clazz.getMethod("hello").invoke(people);
Thread.sleep(2000);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
启动后:
由于就在本级目录,先进行
启动后,改变People的hello()方法再重新一遍javac
便得到
项目结构:
小bug:由于一开始直接name直接写People导致反复不能热加载,后打断点才发现,这个加载器根本没使用到,原来限定名为People导致直接在classpath就被加载了,也就是被父类AppClassLoader加载了。