类加载机制详解
一、知识概述
Java虚拟机的类加载机制是Java语言的核心特性之一。JVM将描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。理解类加载机制对于理解Java程序运行原理、框架开发和问题排查都至关重要。
类加载过程概览
┌─────────────────────────────────────────────────────────────────────┐
│ 类加载完整流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 加载 │──►│ 连接 │──►│ 初始化 │──►│ 使用 │ │
│ │ Loading │ │ Linking │ │Initialize│ │ Using │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 连接细分 │ │
│ ├─────────────────────┤ │
│ │ ┌─────────┐ │ │
│ │ │ 验证 │ │ │
│ │ │ Verify │ │ │
│ │ └────┬────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────┐ │ │
│ │ │ 准备 │ │ │
│ │ │ Prepare │ │ │
│ │ └────┬────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────┐ │ │
│ │ │ 解析 │ │ │
│ │ │ Resolve │ │ │
│ │ └─────────┘ │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
类加载的时机
| 阶段 | 触发条件 |
|---|---|
| 加载 | 遇到new/getstatic/putstatic/invokestatic四条指令 |
| 加载 | 使用java.lang.reflect包方法反射调用 |
| 加载 | 初始化类的子类时,父类需要先初始化 |
| 加载 | 虚拟机启动时的主类(main方法所在类) |
| 加载 | MethodHandle/VarHandle相关的类 |
二、知识点详细讲解
2.1 类加载过程
2.1.1 加载(Loading)
/**
* 类加载阶段详解
*
* 加载阶段完成的三个任务:
* 1. 通过类的全限定名获取定义此类的二进制字节流
* 2. 将字节流代表的静态存储结构转化为方法区的运行时数据结构
* 3. 在内存中生成一个代表这个类的java.lang.Class对象
*/
public class LoadingDemo {
public static void main(String[] args) {
System.out.println("=== 类加载阶段 ===\n");
/*
加载源:
1. 本地文件系统
- CLASSPATH下的.class文件
- JAR包中的.class文件
2. 网络
- Applet从网络加载
- 动态代理生成的类
3. ZIP压缩包
- JAR、WAR文件
4. 运行时计算生成
- 动态代理:$Proxy类
- ASM、CGLib生成的类
5. 数据库
- 从数据库读取字节码
6. 其他文件
- JSP编译成的class
*/
// 示例:观察类加载过程
loadClassDemo();
}
/**
* 观察类加载过程
*/
private static void loadClassDemo() {
System.out.println("【类加载示例】\n");
// 获取类加载器
ClassLoader loader = LoadingDemo.class.getClassLoader();
System.out.println("类加载器: " + loader);
// 加载类(使用Class.forName触发加载)
try {
// forName会执行加载、连接、初始化
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println("加载成功: " + clazz.getName());
// 获取类信息
System.out.println("类名: " + clazz.getSimpleName());
System.out.println("包名: " + clazz.getPackage().getName());
System.out.println("修饰符: " + java.lang.reflect.Modifier.toString(clazz.getModifiers()));
// Class对象在堆中,类的元数据在方法区
System.out.println("\nClass对象存储在: Java堆");
System.out.println("类元数据存储在: 方法区(元空间)");
} catch (ClassNotFoundException e) {
System.out.println("类未找到: " + e.getMessage());
}
System.out.println();
// 使用ClassLoader.loadClass只加载不初始化
System.out.println("ClassLoader.loadClass区别:");
System.out.println(" Class.forName(): 加载+连接+初始化");
System.out.println(" ClassLoader.loadClass(): 仅加载");
}
}
/**
* 验证类加载顺序
* 使用-XX:+TraceClassLoading参数查看
*/
class ClassLoadingOrder {
static {
System.out.println("ClassLoadingOrder 类初始化");
}
public static void main(String[] args) {
System.out.println("main方法开始");
// 使用new触发类加载
new Child();
System.out.println("main方法结束");
}
}
class Parent {
static {
System.out.println("Parent 静态代码块");
}
{
System.out.println("Parent 实例代码块");
}
public Parent() {
System.out.println("Parent 构造方法");
}
}
class Child extends Parent {
static {
System.out.println("Child 静态代码块");
}
{
System.out.println("Child 实例代码块");
}
public Child() {
System.out.println("Child 构造方法");
}
}
/*
执行结果:
ClassLoadingOrder 类初始化
main方法开始
Parent 静态代码块
Child 静态代码块
Parent 实例代码块
Parent 构造方法
Child 实例代码块
Child 构造方法
main方法结束
*/
2.1.2 连接(Linking)
/**
* 连接阶段详解
* 包含:验证、准备、解析三个子阶段
*/
public class LinkingDemo {
public static void main(String[] args) {
System.out.println("=== 连接阶段 ===\n");
verifyDemo();
prepareDemo();
resolveDemo();
}
/**
* 验证阶段(Verify)
*/
private static void verifyDemo() {
System.out.println("【1. 验证阶段】\n");
/*
验证内容:
1. 文件格式验证
- 是否以魔数0xCAFEBABE开头
- 主次版本号是否在当前虚拟机支持范围
- 常量池的常量是否有不支持的类型
- 索引值是否指向正确的位置
2. 元数据验证
- 类是否有父类(除了Object)
- 父类是否继承了不允许继承的类(final类)
- 是否实现了接口的所有方法
- 字段、方法是否与父类冲突
3. 字节码验证
- 操作数栈的数据类型与指令代码序列匹配
- 跳转指令不会跳转到方法体外
- 类型转换是否有效
4. 符号引用验证
- 符号引用的全限定名是否能找到对应的类
- 符号引用中的类、字段、方法是否可访问
*/
System.out.println("验证目的: 确保Class文件字节流符合要求");
System.out.println("验证内容:");
System.out.println(" 1. 文件格式验证 - Class文件格式正确");
System.out.println(" 2. 元数据验证 - 语义分析");
System.out.println(" 3. 字节码验证 - 数据流和控制流分析");
System.out.println(" 4. 符号引用验证 - 引用的类、方法存在且可访问");
System.out.println();
// 魔数示例
System.out.println("Class文件开头:");
System.out.println(" CA FE BA BE 00 00 00 34 ...");
System.out.println(" (魔数0xCAFEBABE,版本52.0 = JDK 8)");
System.out.println();
// 关闭验证(不推荐)
System.out.println("关闭验证参数: -Xverify:none (不推荐)");
System.out.println();
}
/**
* 准备阶段(Prepare)
*/
private static void prepareDemo() {
System.out.println("【2. 准备阶段】\n");
/*
准备阶段任务:
为类的静态变量分配内存,并设置默认初始值
注意:
- 此时只分配内存,不执行<clinit>方法
- 基本类型设置为0值
- 引用类型设置为null
- final类型在准备阶段就会被赋值
*/
System.out.println("准备阶段为静态变量分配内存并设置初始值:\n");
System.out.println("示例代码:");
System.out.println(" public static int value = 123;");
System.out.println(" public static final int CONST = 456;");
System.out.println();
System.out.println("准备阶段后:");
System.out.println(" value = 0 (初始值,非123)");
System.out.println(" CONST = 456 (final变量,直接赋值)");
System.out.println();
System.out.println("初始化阶段后:");
System.out.println(" value = 123 (执行<clinit>方法后)");
System.out.println();
System.out.println("默认初始值表:");
System.out.println(" int -> 0");
System.out.println(" long -> 0L");
System.out.println(" float -> 0.0f");
System.out.println(" double -> 0.0d");
System.out.println(" boolean -> false");
System.out.println(" char -> '\\u0000'");
System.out.println(" reference -> null");
System.out.println();
}
/**
* 解析阶段(Resolve)
*/
private static void resolveDemo() {
System.out.println("【3. 解析阶段】\n");
/*
解析阶段任务:
将常量池内的符号引用替换为直接引用
符号引用(Symbolic Reference):
- 用一组符号描述引用的目标
- 与虚拟机实现的内存布局无关
直接引用(Direct Reference):
- 直接指向目标的指针
- 相对偏移量
- 能定位到目标的句柄
*/
System.out.println("符号引用 vs 直接引用:\n");
System.out.println("符号引用:");
System.out.println(" - 字符串形式的引用");
System.out.println(" - 例: java/lang/System.out:Ljava/io/PrintStream;");
System.out.println();
System.out.println("直接引用:");
System.out.println(" - 内存地址或偏移量");
System.out.println(" - 例: 0x7fff1234 (内存地址)");
System.out.println();
System.out.println("解析类型:");
System.out.println(" 1. 类或接口解析");
System.out.println(" 2. 字段解析");
System.out.println(" 3. 方法解析");
System.out.println(" 4. 接口方法解析");
System.out.println();
// 解析示例
System.out.println("示例:");
System.out.println(" String str = new String();");
System.out.println(" // 解析前: str是符号引用 'java/lang/String'");
System.out.println(" // 解析后: str是指向String类对象的直接引用");
System.out.println();
}
}
/**
* 准备阶段示例
*/
class PrepareExample {
// 准备阶段: value = 0
// 初始化阶段: value = 123
public static int value = 123;
// 准备阶段: CONST = 456 (final直接赋值)
public static final int CONST = 456;
// 准备阶段: obj = null
// 初始化阶段: obj = new Object()
public static Object obj = new Object();
}
2.1.3 初始化(Initialization)
/**
* 初始化阶段详解
*
* 初始化阶段是执行类构造器<clinit>()方法的过程
*/
public class InitializationDemo {
public static void main(String[] args) {
System.out.println("=== 初始化阶段 ===\n");
clinitDemo();
initTriggerDemo();
clinitRules();
}
/**
* <clinit>方法示例
*/
private static void clinitDemo() {
System.out.println("【<clinit>方法】\n");
/*
<clinit>方法特点:
1. 由编译器自动收集类中的所有类变量的赋值动作
和静态语句块(static{})中的语句合并产生
2. 收集顺序由源文件中出现的顺序决定
3. 静态语句块只能访问定义在它之前的变量
4. 先执行父类的<clinit>,再执行子类的<clinit>
5. 如果类没有静态变量和静态代码块,则不生成<clinit>
6. <clinit>方法是线程安全的
*/
System.out.println("<clinit>方法生成规则:");
System.out.println(" - 收集所有静态变量赋值");
System.out.println(" - 收集所有静态代码块");
System.out.println(" - 按源码顺序合并");
System.out.println();
System.out.println("示例代码:");
System.out.println(" public static int a = 1;");
System.out.println(" static { a = 2; }");
System.out.println(" public static int b = 3;");
System.out.println();
System.out.println("<clinit>方法内容:");
System.out.println(" a = 1;");
System.out.println(" a = 2; // 静态代码块");
System.out.println(" b = 3;");
System.out.println(" // 最终 a=2, b=3");
System.out.println();
}
/**
* 初始化触发条件
*/
private static void initTriggerDemo() {
System.out.println("【初始化触发条件】\n");
/*
主动使用(会触发初始化):
1. 遇到new、getstatic、putstatic、invokestatic指令
- new关键字实例化对象
- 读取/设置静态字段(除final)
- 调用静态方法
2. 使用java.lang.reflect包反射调用
3. 初始化类的子类时,父类先初始化
4. 虚拟机启动时的主类(main方法所在类)
5. MethodHandle/VarHandle解析时
被动使用(不会触发初始化):
- 通过子类引用父类的静态字段
- 通过数组定义引用类
- 引用常量(final static)
*/
System.out.println("主动使用(触发初始化):");
System.out.println(" 1. new关键字");
System.out.println(" 2. 访问静态变量(非final)");
System.out.println(" 3. 调用静态方法");
System.out.println(" 4. 反射调用");
System.out.println(" 5. 初始化子类");
System.out.println(" 6. 主类(main方法所在)");
System.out.println();
System.out.println("被动使用(不触发初始化):");
System.out.println(" 1. 子类引用父类静态变量");
System.out.println(" 2. 数组定义引用类");
System.out.println(" 3. 引用final常量");
System.out.println();
}
/**
* 初始化规则示例
*/
private static void clinitRules() {
System.out.println("【初始化规则示例】\n");
// 规则1: 父类先初始化
System.out.println("规则1: 父类先于子类初始化");
// new ChildClass(); // 会触发ParentClass初始化
System.out.println();
// 规则2: final常量不触发初始化
System.out.println("规则2: final常量不触发初始化");
System.out.println(" System.out.println(ConstClass.HELLO);");
System.out.println(" // 不会触发ConstClass初始化");
System.out.println();
// 规则3: 数组不触发初始化
System.out.println("规则3: 数组定义不触发初始化");
System.out.println(" InitClass[] arr = new InitClass[10];");
System.out.println(" // 不会触发InitClass初始化");
System.out.println();
}
}
// 示例类
class ParentClass {
static {
System.out.println("ParentClass 初始化");
}
}
class ChildClass extends ParentClass {
static {
System.out.println("ChildClass 初始化");
}
}
class ConstClass {
static {
System.out.println("ConstClass 初始化");
}
public static final String HELLO = "Hello";
}
class InitClass {
static {
System.out.println("InitClass 初始化");
}
}
/**
* 初始化顺序完整示例
*/
class InitializationOrder {
// 静态变量
private static int staticVar = print("静态变量");
// 实例变量
private int instanceVar = print("实例变量");
// 静态代码块
static {
print("静态代码块");
}
// 实例代码块
{
print("实例代码块");
}
// 构造方法
public InitializationOrder() {
print("构造方法");
}
private static int print(String msg) {
System.out.println(msg);
return 0;
}
public static void main(String[] args) {
System.out.println("=== 创建第一个对象 ===");
new InitializationOrder();
System.out.println("\n=== 创建第二个对象 ===");
new InitializationOrder();
}
/*
执行结果:
静态变量
静态代码块
=== 创建第一个对象 ===
实例变量
实例代码块
构造方法
=== 创建第二个对象 ===
实例变量
实例代码块
构造方法
说明:静态变量和静态代码块只执行一次
*/
}
2.2 类加载器
/**
* 类加载器详解
*/
public class ClassLoaderDemo {
public static void main(String[] args) {
System.out.println("=== 类加载器 ===\n");
showClassLoaderHierarchy();
customClassLoader();
classLoaderMethods();
}
/**
* 类加载器层次结构
*/
private static void showClassLoaderHierarchy() {
System.out.println("【类加载器层次结构】\n");
/*
┌─────────────────────────────────────────────────────────┐
│ 类加载器层次结构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Bootstrap ClassLoader │ │
│ │ (启动类加载器) │ │
│ │ │ │
│ │ 加载: JAVA_HOME/lib/rt.jar, resources.jar等 │ │
│ │ 实现: C++实现,Java中为null │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ extends │
│ ┌──────────────────────▼──────────────────────────┐ │
│ │ Extension ClassLoader │ │
│ │ (扩展类加载器) [JDK 8] │ │
│ │ Platform ClassLoader │ │
│ │ (平台类加载器) [JDK 9+] │ │
│ │ │ │
│ │ 加载: JAVA_HOME/lib/ext/*.jar │ │
│ │ 或 java.ext.dirs 指定的目录 │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ extends │
│ ┌──────────────────────▼──────────────────────────┐ │
│ │ Application ClassLoader │ │
│ │ (应用程序类加载器) │ │
│ │ │ │
│ │ 加载: CLASSPATH下的类 │ │
│ │ 用户自定义的类 │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ extends │
│ ┌──────────────────────▼──────────────────────────┐ │
│ │ Custom ClassLoader │ │
│ │ (自定义类加载器) │ │
│ │ │ │
│ │ 如: Tomcat、OSGi、Spring等框架实现 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
*/
// 获取当前类的类加载器
ClassLoader appClassLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("当前类加载器: " + appClassLoader);
System.out.println(" 类型: " + appClassLoader.getClass().getName());
// 获取父加载器
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("\n父加载器: " + extClassLoader);
System.out.println(" 类型: " + extClassLoader.getClass().getName());
// 获取启动类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("\n启动类加载器: " + bootstrapClassLoader);
System.out.println(" (C++实现,Java中表示为null)");
// 查看各类加载器加载的类
System.out.println("\n【各类加载器加载的类】\n");
// 核心类由启动类加载器加载
Class<?> stringClass = String.class;
System.out.println("String类加载器: " + stringClass.getClassLoader());
System.out.println(" (null = Bootstrap ClassLoader)");
// 扩展类
Class<?> curveClass = sun.security.ec.CurveDB.class;
System.out.println("\nCurveDB类加载器: " + curveClass.getClassLoader());
// 应用类
System.out.println("\n当前类加载器: " + ClassLoaderDemo.class.getClassLoader());
System.out.println();
}
/**
* 类加载器关键方法
*/
private static void classLoaderMethods() {
System.out.println("【类加载器关键方法】\n");
System.out.println("ClassLoader核心方法:");
System.out.println();
System.out.println("1. loadClass(String name)");
System.out.println(" - 加载指定名称的类");
System.out.println(" - 遵循双亲委派模型");
System.out.println();
System.out.println("2. findClass(String name)");
System.out.println(" - 查找类的字节码");
System.out.println(" - 自定义类加载器重写此方法");
System.out.println();
System.out.println("3. defineClass(String name, byte[] b, int off, int len)");
System.out.println(" - 将字节数组转换为Class对象");
System.out.println(" - 必须在findClass中调用");
System.out.println();
System.out.println("4. findLoadedClass(String name)");
System.out.println(" - 检查类是否已加载");
System.out.println(" - 在loadClass开头调用");
System.out.println();
System.out.println("5. getParent()");
System.out.println(" - 获取父类加载器");
System.out.println();
}
/**
* 自定义类加载器
*/
private static void customClassLoader() {
System.out.println("【自定义类加载器示例】\n");
/*
自定义类加载器步骤:
1. 继承ClassLoader类
2. 重写findClass方法
3. 在findClass中读取类字节码
4. 调用defineClass生成Class对象
*/
System.out.println("自定义类加载器用途:");
System.out.println(" 1. 从非标准来源加载类(网络、数据库)");
System.out.println(" 2. 实现类的隔离(同一类多版本共存)");
System.out.println(" 3. 实现热部署");
System.out.println(" 4. 加密解密类文件");
System.out.println();
}
}
/**
* 自定义类加载器示例
*/
class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
public CustomClassLoader(ClassLoader parent, String classPath) {
super(parent);
this.classPath = classPath;
}
/**
* 重写findClass方法
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 读取类文件的字节码
byte[] data = loadClassData(name);
// 将字节码转换为Class对象
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
throw new ClassNotFoundException(name, e);
}
}
/**
* 从文件读取类字节码
*/
private byte[] loadClassData(String className) throws Exception {
String fileName = classPath + className.replace('.', '/') + ".class";
java.io.FileInputStream fis = new java.io.FileInputStream(fileName);
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
fis.close();
return baos.toByteArray();
}
/**
* 测试自定义类加载器
*/
public static void main(String[] args) throws Exception {
// 创建自定义类加载器
CustomClassLoader loader = new CustomClassLoader("/tmp/classes/");
// 加载类
Class<?> clazz = loader.loadClass("com.example.TestClass");
System.out.println("类名: " + clazz.getName());
System.out.println("类加载器: " + clazz.getClassLoader());
}
}
2.3 双亲委派模型
/**
* 双亲委派模型详解
*/
public class ParentDelegationDemo {
public static void main(String[] args) {
System.out.println("=== 双亲委派模型 ===\n");
explainModel();
showModelCode();
breakModel();
advantages();
}
/**
* 双亲委派模型解释
*/
private static void explainModel() {
System.out.println("【双亲委派模型】\n");
/*
双亲委派模型工作流程:
1. 类加载器收到类加载请求
2. 首先把请求委派给父类加载器
3. 父类加载器反馈无法完成(未找到类)时
4. 子类加载器才会尝试自己加载
流程图:
┌─────────────────────────────────────────────────────┐
│ │
│ Application ClassLoader │
│ 收到加载请求 │
│ │ │
│ │ 1.委派给父加载器 │
│ ▼ │
│ Extension ClassLoader │
│ │ │
│ │ 2.委派给父加载器 │
│ ▼ │
│ Bootstrap ClassLoader │
│ │ │
│ │ 3.尝试加载 │
│ │ │
│ ┌─────┴─────┐ │
│ │ 找到? │ │
│ └─────┬─────┘ │
│ 是 ↙ ↘ 否 │
│ 返回 继续向下 │
│ │ │
│ Extension ClassLoader │
│ │ │
│ ┌────┴────┐ │
│ │ 找到? │ │
│ └────┬────┘ │
│ 是 ↙ ↘ 否 │
│ 返回 继续向下 │
│ │ │
│ Application ClassLoader │
│ │ │
│ ┌────┴────┐ │
│ │ 找到? │ │
│ └────┬────┘ │
│ 是 ↙ ↘ 否 │
│ 返回 抛出ClassNotFoundException │
│ │
└─────────────────────────────────────────────────────┘
*/
System.out.println("双亲委派模型:");
System.out.println(" 1. 收到加载请求先委派给父加载器");
System.out.println(" 2. 父加载器无法完成时,子加载器才尝试");
System.out.println(" 3. 保证类的唯一性和安全性");
System.out.println();
}
/**
* 双亲委派模型代码实现
*/
private static void showModelCode() {
System.out.println("【ClassLoader.loadClass实现】\n");
String code = """
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 1. 检查类是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 委派给父加载器
c = parent.loadClass(name, false);
} else {
// 3. 没有父加载器,使用启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载器无法完成,捕获异常
}
if (c == null) {
// 4. 父加载器无法完成,自己尝试加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
""";
System.out.println(code);
System.out.println();
}
/**
* 打破双亲委派模型
*/
private static void breakModel() {
System.out.println("【打破双亲委派模型的场景】\n");
System.out.println("1. JDK 1.2之前(无双亲委派)");
System.out.println(" - 用户继承ClassLoader并重写loadClass");
System.out.println();
System.out.println("2. SPI机制(Service Provider Interface)");
System.out.println(" - JDBC: java.sql.Driver由启动类加载器加载");
System.out.println(" - 实现: mysql-connector等由应用类加载器加载");
System.out.println(" - 解决: Thread.setContextClassLoader()");
System.out.println();
System.out.println("3. OSGi、Jigsaw等模块化系统");
System.out.println(" - 类加载器形成网状结构");
System.out.println(" - 每个模块有自己的类加载器");
System.out.println();
System.out.println("4. Tomcat等Web容器");
System.out.println(" - 每个WebApp独立的类加载器");
System.out.println(" - 优先加载WebApp自己的类");
System.out.println();
// 打破双亲委派的示例代码
System.out.println("打破双亲委派的代码模式:");
System.out.println("""
@Override
protected Class<?> loadClass(String name, boolean resolve) {
// 1. 先检查已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
// 2. 某些类需要特殊处理
if (name.startsWith("com.myapp.")) {
// 自己先加载,不委派给父加载器
c = findClass(name);
} else {
// 其他类走双亲委派
c = super.loadClass(name, resolve);
}
}
return c;
}
""");
System.out.println();
}
/**
* 双亲委派模型的优点
*/
private static void advantages() {
System.out.println("【双亲委派模型的优点】\n");
System.out.println("1. 安全性");
System.out.println(" - 核心类由启动类加载器加载");
System.out.println(" - 用户无法伪造核心类(如java.lang.String)");
System.out.println();
System.out.println("2. 唯一性");
System.out.println(" - 同一个类只会被加载一次");
System.out.println(" - 所有类加载器看到的都是同一个类");
System.out.println();
System.out.println("3. 一致性");
System.out.println(" - 避免类的重复加载");
System.out.println(" - 保证Java程序稳定运行");
System.out.println();
// 演示无法伪造核心类
System.out.println("【演示:无法伪造核心类】\n");
// 尝试定义java.lang.String
System.out.println("尝试创建 java.lang.MyString:");
System.out.println(" 即使创建成功,也会由启动类加载器加载");
System.out.println(" 不会替换核心类 java.lang.String");
System.out.println();
// 禁止自定义java.lang包下的类
System.out.println("禁止定义java.开头的包:");
System.out.println(" java.lang.SecurityException: Prohibited package name: java.lang");
System.out.println();
}
}
/**
* 演示双亲委派保证类的唯一性
*/
class ClassUniquenessDemo {
public static void main(String[] args) {
// 不同类加载器加载同一个类
ClassLoader appLoader = ClassUniquenessDemo.class.getClassLoader();
// 获取String类(由启动类加载器加载)
Class<?> stringClass1 = String.class;
Class<?> stringClass2 = "hello".getClass();
System.out.println("String.class == \"hello\".getClass(): " +
(stringClass1 == stringClass2)); // true
System.out.println("类加载器: " + stringClass1.getClassLoader()); // null (Bootstrap)
// 同一个类由不同类加载器加载,是不同的类
System.out.println("\n注意:");
System.out.println(" 同一个Class文件,不同类加载器加载 -> 不同的Class对象");
System.out.println(" instanceof比较返回false");
}
}
2.4 打破双亲委派模型
import java.io.*;
/**
* 打破双亲委派模型示例
* 实现类的隔离和热加载
*/
public class BreakParentDelegationDemo {
public static void main(String[] args) throws Exception {
System.out.println("=== 打破双亲委派模型示例 ===\n");
// 1. 实现类隔离
classIsolation();
// 2. 实现热加载
hotReload();
}
/**
* 类隔离示例:同一类多版本共存
*/
private static void classIsolation() throws Exception {
System.out.println("【类隔离示例】\n");
// 创建两个不同的类加载器
IsolatingClassLoader loader1 = new IsolatingClassLoader("/version1/");
IsolatingClassLoader loader2 = new IsolatingClassLoader("/version2/");
// 加载同一个类名,但内容不同
Class<?> clazz1 = loader1.loadClass("com.example.VersionClass");
Class<?> clazz2 = loader2.loadClass("com.example.VersionClass");
System.out.println("类名相同: " + clazz1.getName() + " == " + clazz2.getName());
System.out.println("Class对象相同: " + (clazz1 == clazz2)); // false
System.out.println("\n说明:");
System.out.println(" 同名类由不同类加载器加载,是不同的类");
System.out.println(" 可实现同一应用内多版本共存");
System.out.println();
/*
应用场景:
- Tomcat: 多个WebApp使用不同版本的库
- OSGi: 每个Bundle有自己的类加载器
- 插件系统: 插件间相互隔离
*/
}
/**
* 热加载示例
*/
private static void hotReload() throws Exception {
System.out.println("【热加载示例】\n");
// 加载类并执行
HotReloadingClassLoader loader = new HotReloadingClassLoader("/hotload/");
Class<?> clazz = loader.loadClass("com.example.HotClass");
Object instance = clazz.newInstance();
java.lang.reflect.Method method = clazz.getMethod("sayHello");
method.invoke(instance);
System.out.println("\n修改类文件后,创建新的类加载器...");
// 创建新的类加载器加载修改后的类
HotReloadingClassLoader newLoader = new HotReloadingClassLoader("/hotload/");
Class<?> newClazz = newLoader.loadClass("com.example.HotClass");
Object newInstance = newClazz.newInstance();
java.lang.reflect.Method newMethod = newClazz.getMethod("sayHello");
newMethod.invoke(newInstance);
System.out.println("\n说明:");
System.out.println(" 每次创建新的类加载器,可加载修改后的类");
System.out.println(" 实现代码修改后无需重启应用");
System.out.println();
}
}
/**
* 隔离类加载器:打破双亲委派
*/
class IsolatingClassLoader extends ClassLoader {
private String classPath;
public IsolatingClassLoader(String classPath) {
this.classPath = classPath;
}
/**
* 重写loadClass,打破双亲委派
*/
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
// 2. java核心类还是委派给父加载器
if (name.startsWith("java.")) {
c = super.loadClass(name, resolve);
} else {
// 3. 其他类自己先加载(打破双亲委派)
try {
c = findClass(name);
} catch (ClassNotFoundException e) {
// 4. 找不到再委派给父加载器
c = super.loadClass(name, resolve);
}
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
private byte[] loadClassData(String className) throws IOException {
String fileName = classPath + className.replace('.', '/') + ".class";
try (FileInputStream fis = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
}
}
}
/**
* 热加载类加载器
*/
class HotReloadingClassLoader extends ClassLoader {
private String classPath;
public HotReloadingClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
private byte[] loadClassData(String className) throws IOException {
String fileName = classPath + className.replace('.', '/') + ".class";
try (FileInputStream fis = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
}
}
}
三、可运行Java代码示例
完整示例:SPI机制与线程上下文类加载器
import java.sql.*;
import java.util.*;
/**
* SPI机制示例
* Service Provider Interface - 服务提供者接口
*/
public class SPIDemo {
public static void main(String[] args) {
System.out.println("=== SPI机制详解 ===\n");
explainSPI();
contextClassLoaderDemo();
implementSPI();
}
/**
* SPI机制解释
*/
private static void explainSPI() {
System.out.println("【SPI机制】\n");
/*
SPI (Service Provider Interface):
- 定义接口在核心库中(由启动类加载器加载)
- 实现类由第三方提供(由应用类加载器加载)
- 打破双亲委派模型的经典场景
典型SPI:
- JDBC: java.sql.Driver (接口) -> mysql-connector (实现)
- JNDI: javax.naming.spi.InitialContextFactory
- JAXP: javax.xml.parsers.DocumentBuilderFactory
- JAXB: javax.xml.bind.JAXBContext
*/
System.out.println("SPI问题:");
System.out.println(" 接口: java.sql.Driver (启动类加载器)");
System.out.println(" 实现: com.mysql.jdbc.Driver (应用类加载器)");
System.out.println();
System.out.println("问题:");
System.out.println(" 启动类加载器无法「向下」加载应用类加载器的类");
System.out.println();
System.out.println("解决方案:");
System.out.println(" 线程上下文类加载器 (Context ClassLoader)");
System.out.println();
}
/**
* 线程上下文类加载器
*/
private static void contextClassLoaderDemo() {
System.out.println("【线程上下文类加载器】\n");
// 获取当前线程的上下文类加载器
Thread thread = Thread.currentThread();
ClassLoader contextLoader = thread.getContextClassLoader();
System.out.println("当前线程: " + thread.getName());
System.out.println("上下文类加载器: " + contextLoader);
System.out.println("类加载器类型: " + contextLoader.getClass().getName());
System.out.println();
/*
上下文类加载器的作用:
1. 启动类加载器加载的类需要访问应用类加载器加载的类
2. JDK核心类库通过线程上下文类加载器加载SPI实现
3. 默认情况下,上下文类加载器是应用类加载器
代码示例(JDBC):
// DriverManager (启动类加载器)
public class DriverManager {
static {
// 使用上下文类加载器加载Driver实现
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers =
ServiceLoader.load(Driver.class);
// ...
}
});
}
}
// ServiceLoader.load
public static <S> ServiceLoader<S> load(Class<S> service) {
// 使用上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(service, cl);
}
*/
System.out.println("SPI加载流程:");
System.out.println(" 1. DriverManager由启动类加载器加载");
System.out.println(" 2. DriverManager使用ServiceLoader加载Driver");
System.out.println(" 3. ServiceLoader使用线程上下文类加载器");
System.out.println(" 4. 上下文类加载器是应用类加载器");
System.out.println(" 5. 成功加载mysql-connector的Driver实现");
System.out.println();
}
/**
* 实现自定义SPI
*/
private static void implementSPI() {
System.out.println("【自定义SPI示例】\n");
// 使用ServiceLoader加载服务实现
ServiceLoader<SPIDemoService> services =
ServiceLoader.load(SPIDemoService.class);
System.out.println("加载 SPI 实现类:");
for (SPIDemoService service : services) {
System.out.println(" - " + service.getClass().getName());
service.execute();
}
System.out.println();
System.out.println("SPI配置文件位置:");
System.out.println(" META-INF/services/完整接口名");
System.out.println();
System.out.println("示例:");
System.out.println(" 文件: META-INF/services/com.example.SPIDemoService");
System.out.println(" 内容: com.example.impl.SPIDemoServiceImpl");
System.out.println();
}
}
/**
* SPI接口
*/
interface SPIDemoService {
void execute();
}
/**
* SPI实现类1
*/
class SPIDemoServiceImpl1 implements SPIDemoService {
@Override
public void execute() {
System.out.println(" SPIDemoServiceImpl1 executed");
}
}
/**
* SPI实现类2
*/
class SPIDemoServiceImpl2 implements SPIDemoService {
@Override
public void execute() {
System.out.println(" SPIDemoServiceImpl2 executed");
}
}
完整示例:Tomcat类加载机制
/**
* Tomcat类加载机制示例
*/
public class TomcatClassLoaderDemo {
public static void main(String[] args) {
System.out.println("=== Tomcat类加载机制 ===\n");
showHierarchy();
explainLoadingOrder();
}
/**
* Tomcat类加载器层次
*/
private static void showHierarchy() {
System.out.println("【Tomcat类加载器层次】\n");
/*
┌─────────────────────────────────────────────────────────┐
│ Tomcat类加载器层次 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Bootstrap ClassLoader │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼──────────────────────────┐ │
│ │ System ClassLoader │ │
│ │ (加载Tomcat核心类) │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼──────────────────────────┐ │
│ │ Common ClassLoader │ │
│ │ (加载Tomcat和WebApp共享类) │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴──────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Catalina │ │ Shared │ │ │
│ │ │ ClassLoader │ │ ClassLoader │ │ │
│ │ └─────────────────┘ └────────┬────────┘ │ │
│ │ │ │ │
│ └───────────────────────────────────┼──────────────┘ │
│ │ │
│ ┌───────────────────────────────────▼──────────────┐ │
│ │ WebApp ClassLoader │ │
│ │ (每个WebApp一个) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌───────────┐│ │
│ │ │ WebApp1 │ │ WebApp2 │ │ WebApp3 ││ │
│ │ │ ClassLoader │ │ ClassLoader │ │ ClassLoad ││ │
│ │ └─────────────┘ └─────────────┘ └───────────┘│ │
│ │ │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
*/
System.out.println("各加载器职责:");
System.out.println();
System.out.println("Bootstrap ClassLoader:");
System.out.println(" 加载JVM核心类库");
System.out.println();
System.out.println("System ClassLoader:");
System.out.println(" 加载Tomcat启动类(catalina.sh指定的类)");
System.out.println();
System.out.println("Common ClassLoader:");
System.out.println(" 加载Tomcat和WebApp共享的类(如Servlet API)");
System.out.println();
System.out.println("Catalina ClassLoader:");
System.out.println(" 加载Tomcat内部实现类(WebApp不可见)");
System.out.println();
System.out.println("Shared ClassLoader:");
System.out.println(" 加载所有WebApp共享的类");
System.out.println();
System.out.println("WebApp ClassLoader:");
System.out.println(" 每个WebApp独立,加载WebApp自己的类");
System.out.println(" 打破双亲委派,优先加载WebApp类");
System.out.println();
}
/**
* Tomcat类加载顺序
*/
private static void explainLoadingOrder() {
System.out.println("【WebApp类加载顺序】\n");
System.out.println("默认加载顺序(打破双亲委派):");
System.out.println(" 1. JVM Bootstrap类");
System.out.println(" 2. WebApp自己类库 (WEB-INF/classes)");
System.out.println(" 3. WebApp依赖库 (WEB-INF/lib)");
System.out.println(" 4. Tomcat共享类库");
System.out.println(" 5. System类库");
System.out.println();
System.out.println("特点:");
System.out.println(" - WebApp类优先于Tomcat共享类");
System.out.println(" - 不同WebApp可以使用不同版本的库");
System.out.println(" - 实现WebApp之间的类隔离");
System.out.println();
System.out.println("配置修改:");
System.out.println(" delegate=\"true\": 遵循双亲委派");
System.out.println(" delegate=\"false\": 打破双亲委派(默认)");
System.out.println();
System.out.println("配置示例 (context.xml):");
System.out.println("""
<Context>
<Loader className="org.apache.catalina.loader.WebappLoader"
delegate="false"/>
</Context>
""");
System.out.println();
}
}
四、实战应用场景
场景1:插件化架构
import java.io.*;
import java.util.*;
/**
* 插件化架构实现
*/
public class PluginDemo {
public static void main(String[] args) throws Exception {
System.out.println("=== 插件化架构 ===\n");
// 创建插件管理器
PluginManager manager = new PluginManager();
// 加载插件
manager.loadPlugin("/plugins/plugin1.jar");
manager.loadPlugin("/plugins/plugin2.jar");
// 执行所有插件
manager.executeAllPlugins();
// 卸载插件
manager.unloadPlugin("plugin1");
}
}
/**
* 插件接口
*/
interface Plugin {
String getName();
void execute();
}
/**
* 插件管理器
*/
class PluginManager {
private Map<String, PluginClassLoader> plugins = new HashMap<>();
/**
* 加载插件
*/
public void loadPlugin(String jarPath) throws Exception {
PluginClassLoader loader = new PluginClassLoader(jarPath);
// 读取插件配置
// META-INF/services/Plugin
ServiceLoader<Plugin> services = ServiceLoader.load(Plugin.class, loader);
for (Plugin plugin : services) {
String name = plugin.getName();
plugins.put(name, loader);
System.out.println("加载插件: " + name);
}
}
/**
* 执行所有插件
*/
public void executeAllPlugins() {
for (Map.Entry<String, PluginClassLoader> entry : plugins.entrySet()) {
PluginClassLoader loader = entry.getValue();
ServiceLoader<Plugin> services =
ServiceLoader.load(Plugin.class, loader);
for (Plugin plugin : services) {
System.out.println("执行插件: " + plugin.getName());
plugin.execute();
}
}
}
/**
* 卸载插件
*/
public void unloadPlugin(String name) {
PluginClassLoader loader = plugins.remove(name);
if (loader != null) {
// 卸载类加载器,允许GC回收类
loader = null;
System.out.println("卸载插件: " + name);
}
}
}
/**
* 插件类加载器
*/
class PluginClassLoader extends ClassLoader {
private String jarPath;
public PluginClassLoader(String jarPath) {
this.jarPath = jarPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 从JAR中读取类字节码并定义类
// 实现略
throw new ClassNotFoundException(name);
}
}
五、总结与最佳实践
核心要点回顾
| 阶段 | 任务 |
|---|---|
| 加载 | 读取Class文件,创建Class对象 |
| 验证 | 校验Class文件格式、语义、字节码 |
| 准备 | 为静态变量分配内存,设置默认值 |
| 解析 | 将符号引用转换为直接引用 |
| 初始化 | 执行<clinit>方法,初始化静态变量 |
类加载器分类
| 加载器 | 加载内容 | 说明 |
|---|---|---|
| Bootstrap | JAVA_HOME/lib/*.jar | C++实现 |
| Extension/Platform | JAVA_HOME/lib/ext/*.jar | Java实现 |
| Application | CLASSPATH下的类 | 默认用户类加载器 |
| Custom | 自定义位置 | 框架自定义实现 |
最佳实践
-
遵循双亲委派
- 除非必要,不要打破双亲委派模型
- 优先使用标准类加载器
-
正确使用SPI
// 使用ServiceLoader加载SPI实现 ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class); -
避免类加载泄漏
- 及时释放自定义类加载器
- ThreadLocal配合使用需注意清理
-
理解初始化顺序
- 父类静态 -> 子类静态 -> 父类实例 -> 子类实例
常见问题
-
ClassNotFoundException vs NoClassDefFoundError
- ClassNotFoundException: 加载时找不到类
- NoClassDefFoundError: 运行时找不到类(类存在但加载失败)
-
类加载死锁
- 多线程并发加载类时可能发生
- 解决:避免在静态初始化块中创建线程
相关JVM参数
# 类加载日志
-verbose:class # 打印类加载信息
-XX:+TraceClassLoading # 跟踪类加载
-XX:+TraceClassUnloading # 跟踪类卸载
# 类路径
-Xbootclasspath # 启动类路径
-Xbootclasspath/a # 追加启动类路径
-Djava.ext.dirs # 扩展类路径
-Djava.class.path # 应用类路径
扩展阅读
- 《深入理解Java虚拟机》:周志明著,类加载机制章节
- JSR 121:Java Application Isolation
- OSGi规范:动态模块化系统
- Tomcat源码:WebappClassLoader实现