一. JVM核心类加载器类别:
- 引导类加载器:负责加载支撑JVM运行的位于jre/lib目录下的核心类库,由C++实现创建
- 扩展类加载器:负责加载支撑JVM运行的位于jre/ext扩展目录下的jar类包,由JVM启动器实例创建加载
- 应用程序类加载器:负责加载ClassPath路径下的类包,由JVM启动器实例创建加载
- 自定义加载器:负责加载用户自定义路径下的类包
二. JVM类加载器初始化过程:
- C++实现创建引导类加载器
- C++创建JVM启动器实例 sun.misc.Launcher,Launcher创建方式才用了单例模式,保证一个JVM中只有一个Launcher实例
- Launcher实例在构造时会创建加载扩展类加载器sun.misc.Launcher.ExtClassLoader实例、应用程序类加载器sun.misc.Launcher.AppClassLoader示例
- JVM默认使用launcher.getClassLoader()返回的类加载器AppClassLoader的实例加载应用程序类
三. JVM类加载器运行全过程:
示例代码:
package com.example.demojvm.jvm;
public class JvmTest {
public static void main(String[] args) {
System.out.print("Hello Word!");
}
}
- 运行JvmTest.main(),会先使用命令 javac java com.example.demojvm.jvm.JvmTest.java 将JvmTest.java编译成.class文件,然后使用命令 java com.example.demojvm.jvm.JvmTest.class 运行JvmTest.class
- 此时底层的C++实现会使用Windows系统下的java.exe程序调用jvm.dll库文件创建Java虚拟机,并创建一个引导类加载器实例。引导类加载器实例会加载 jre/lib 目录下jar包中的java核心类。
- 引导类加载器会创建JVM启动类实例 sun.misc.Launcher,Launcher类会加载创建扩展类加载器实例sun.misc.Launcher.ExtClassLoader、应用程序类加载器实例sun.misc.Launcher.AppClassLoader。
sun.misc.Launcher类源码 :
Launcher类创建时会创建加载扩展类加载器实例sun.misc.Launcher.ExtClassLoader、应用程序类加载器实例sun.misc.Launcher.AppClassLoader
- C++实现调用launcher.getClassLoader()获取到应用程序类加载器实例loader,并调用 loader.loadClass()加载运行的类
- 类加载完成时,JVM会执行JvmTest.main()方法,开始执行代码。
- 代码执行完毕,程序销毁
四. loadClass过程步骤
步骤:
- 加载:在硬盘上查找并通过IO读入字节码文件,在内存中生成一个代表这个类的class对象,作为方法区这个类的各种数据的访问入口
- 验证:校验字节码文件是否符合Java规范 准备:给类的静态变量分配内存,并赋默认值
- 解析:将符号引用替换为直接引用,把一些静态方法替换为指向数据所存内存地址的指针或句柄
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块
- 使用:运行代码
- 卸载:回收运行时常量
五. JVM类加载的双亲委派机制
双亲委派机制原理:
原理:加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载路径下都找不到目标类,则在自己的类加载器中查找并载入目标类
简述:先找父加载器加载,父加载器加载不到再自己加载
JVM类加载过程使用双亲委派机制原因:
- 沙箱安全机制
核心类库只会由引导类加载器去加载,可以防止核心API库被随意篡改
- 避免类的重复加载
如果父加载器已经加载了目标类,子加载器就没必要重复加载,保证被加载类的唯一性
JVM类加载的双亲委派机制加载类过程:
- 先检查指定名称的类自己是否已经加载,如果加载过就无需重复加载,直接返回
- 如果指定名称的类没有加载过,判断是否有父加载器,如果有父加载器则由父加载器加载(调用parent.loadClass(name, false)),如果没有父加载器则调用bootstrap类加载器来加载(其实就是引导类加载器)
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass()方法来完成类加载
JVM类加载双亲委派机制源码解析:
首先JVM默认使用launcher.getClassLoader()返回的类加载器来加载应用程序类,而launcher.getClassLoader()获取到的类加载器其实是在launcher实例化时放进去的应用程序加载器。
appClassLoader.loadClass()先判断是否已加载过目标类,加载过则直接返回,未加载过则调用继承自ClassLoader的loadClass()方法
进入ClassLoader类的loadClass()方法,可以看到如果存在父加载器,会先调用父加载器的loadClass()方法。debugger可以看到appClassLoader的父加载器是ExtClassLoader类的实例。
进入ExtClassLoader类,发现ExtClassLoader类并没有实现loadClass()方法,但是继承了ClassLoader的loadClass()方法。debugger进入,可以看到ExtClassLoader实例的parent属性为null,所以走另一个分支,调用findBootstrapClassOrNull()方法。
因为引导类加载器由C++创建加载,在Java中获取不到,所以ExtClassLoader实例的parent属性为空,调用findBootstrapClassOrNull()其实就是调用引导类加载器加载目标类。
可想而知,JvmTest.class位于ClassPath下,引导类加载器只负责加载jre/lib目录下的核心类包,肯定加载不到JvmTest.class,那么ExtClassLoader加载器实例就会去自己调用findClass()方法加载。
同理ExtClassLoader实例也无法加载到JvmTest.class类,那么AppClassLoader实例也会去自己加载JvmTest.class类。AppClassLoader加载路径包括ClassPath,最终在AppClassLoader加载器实例下加载到JvmTest.class类。
六. 自定义类加载器
根据JVM类加载过程可以发现,实现双亲委派机制的代码为:
那么想要自定义类加载器,实现自己想要的加载逻辑,只需要新建一个类继承ClassLoad类,并重写loadClass()方法就好了。
将ClassLoader的loadClass()方法拷贝过来,修改类加载机制逻辑:
加载逻辑修改了,还需要调用defind()方法加载.class文件生成class对象。
实现findClass()方法,在findClass()方法中调用defind()方法
最后在启动类中调用测试:
public static void main(String[] args) {
try {
JvmTestClassLoader loader = new JvmTestClassLoader("D:/Project/CHAN/demo-jvm/target/classes");
Class> clazz = loader.loadClass("com.example.demojvm.jvm.JvmTest");
Object obj = clazz.newInstance();
ClassLoader classLoader = obj.getClass().getClassLoader();
System.out.print("classLoader = " + classLoader);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
打印结果:
classLoader = com.example.demojvm.jvm.JvmTest$JvmTestClassLoader@27c170f0
打印结果为自定义的JvmTest.JvmTestClassLoader,成功实现自定义类加载器,并打破JVM类加载器双亲委派机制
完整代码:
package com.example.demojvm.jvm;
import org.springframework.util.StringUtils;
import java.io.FileInputStream;
import java.io.IOException;
public class JvmTest {
public static void main(String[] args) {
try {
JvmTestClassLoader loader = new JvmTestClassLoader("D:/Project/CHAN/demo-jvm/target/classes");
Class> clazz = loader.loadClass("com.example.demojvm.jvm.JvmTest");
Object obj = clazz.newInstance();
ClassLoader classLoader = obj.getClass().getClassLoader();
System.out.print("classLoader = " + classLoader);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
static class JvmTestClassLoader extends ClassLoader{
private String classPath;
public JvmTestClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws IOException {
if(!StringUtils.hasLength(name)) {
throw new RuntimeException("name can not be null or ''");
}
name = name.replaceAll("\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
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 (name.startsWith("com.example.demojvm")) {
c = findClass(name);
} else {
c = this.getParent().loadClass(name);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = loadByte(name);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException(e.getMessage());
}
}
}
}
待解决问题:
- ExtClassLoader加载器实例加载jre/ext扩展目录下jar类包,由谁发起调用?什么时候调用?



