一. JVM核心类加载器类别:
类别:
- 引导类加载器:负责加载支撑JVM运行的位于
jre/lib
目录下的核心类库,由C++实现创建- 扩展类加载器:负责加载支撑JVM运行的位于
jre/ext
扩展目录下的jar类包,由JVM启动器实例创建加载- 应用程序类加载器:负责加载
ClassPath
路径下的类包,由JVM启动器实例创建加载- 自定义加载器:负责加载用户自定义路径下的类包
层级关系:
其中各层级类加载器并无直接的父子继承关系,而是根据其中的parent属性来判断父加载器是谁。
自定义类加载器需继承ClassLoader类,对于ClassLoader类其官方解释如下:
二. 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对象,作为方法区这个类的各种数据的访问入口。
"加载"是"类加载"过程的一个阶段,此阶段完成的功能是:
1)通过类的全限定名来获取定义此类的二进制字节流
2)将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构
3)在内存中生成代表此类的java.lang.Class对象,作为该类访问入口.- 验证:连接阶段第一步,校验字节码文件是否符合Java规范 ,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击.大致完成以下四个校验动作:
1)文件格式验证
2)源数据验证
3)字节码验证
4)符号引用验证- 准备:连接阶段第二步,给类的变量分配内存,并赋默认值(仅包含类变量,不包含实例变量)
- 解析:连接阶段第三步,虚拟机将常量池中的符号引用替换为直接引用,把一些静态方法替换为指向数据所存内存地址的指针或句柄 。解析动作主要针对类或接口,字段,类方法,方法类型等等。
- 初始化:类加载过程的最后一步,对类的静态变量初始化为指定的值,执行静态代码块、构造器。在该阶段,才真正意义上的开始执行类中定义的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;
/**
* JVM类加载机制测试Demo
* @author 青袂
* @date 2021-11-12
* @desc JVM类加载机制测试Demo
*/
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;
}
}
/**
* 加载目标.class文件 生成对象
*/
@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类包,由谁发起调用?什么时候调用?