类加载机制
2026/1/15大约 4 分钟
类加载机制
类的生命周期
加载(Loading)
将类的字节码加载到内存中。
- 通过类的全限定名获取二进制字节流
- 将字节流转换为方法区的运行时数据结构
- 在堆中生成 Class 对象
加载来源
- 本地文件系统
- JAR/WAR 包
- 网络(Applet)
- 动态代理生成
- JSP 生成
验证(Verification)
确保字节码符合 JVM 规范。
| 验证阶段 | 内容 |
|---|---|
| 文件格式验证 | 魔数、版本号、常量池 |
| 元数据验证 | 语义分析(继承、抽象方法) |
| 字节码验证 | 数据流和控制流分析 |
| 符号引用验证 | 类、字段、方法是否存在 |
准备(Preparation)
为类变量分配内存并设置初始值。
public static int value = 123;
// 准备阶段:value = 0
// 初始化阶段:value = 123
public static final int CONST = 123;
// 准备阶段:CONST = 123(常量直接赋值)解析(Resolution)
将符号引用转换为直接引用。
| 符号引用 | 直接引用 |
|---|---|
| 类的全限定名 | 内存地址 |
| 字段名和描述符 | 偏移量 |
| 方法名和描述符 | 方法入口地址 |
初始化(Initialization)
执行类构造器 <clinit>() 方法。
触发初始化的情况
- new、getstatic、putstatic、invokestatic 指令
- 反射调用
- 初始化子类时,先初始化父类
- 主类(包含 main 方法的类)
- MethodHandle 解析结果为 REF_getStatic 等
不会触发初始化
// 1. 通过子类引用父类的静态字段
class Parent {
static int value = 1;
}
class Child extends Parent {}
System.out.println(Child.value); // 不会初始化 Child
// 2. 数组定义
Parent[] arr = new Parent[10]; // 不会初始化 Parent
// 3. 常量
public static final int CONST = 123;
System.out.println(SomeClass.CONST); // 不会初始化 SomeClassclinit 方法
public class Test {
static int a = 1;
static {
a = 2;
b = 2; // 可以赋值
// System.out.println(b); // 编译错误,不能访问
}
static int b = 1;
}
// 最终:a = 2, b = 1类加载器
类加载器层次
各加载器职责
| 类加载器 | 加载路径 | 实现 |
|---|---|---|
| Bootstrap | JAVA_HOME/lib | C++ 实现 |
| Extension | JAVA_HOME/lib/ext | Java 实现 |
| Application | classpath | Java 实现 |
// 获取类加载器
ClassLoader loader = String.class.getClassLoader();
System.out.println(loader); // null(Bootstrap)
ClassLoader appLoader = Test.class.getClassLoader();
System.out.println(appLoader); // AppClassLoader
System.out.println(appLoader.getParent()); // ExtClassLoader
System.out.println(appLoader.getParent().getParent()); // null双亲委派模型
工作流程
源码实现
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 2. 委派给父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// 3. 自己加载
c = findClass(name);
}
}
return c;
}
}双亲委派的意义
- 避免重复加载:父加载器加载过的类不会重复加载
- 保证安全性:防止核心类被篡改
// 即使自定义 java.lang.String,也会被 Bootstrap 加载器加载
// 保证了核心类的安全打破双亲委派
1. 重写 loadClass 方法
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 不委派给父加载器,直接自己加载
if (name.startsWith("com.myapp")) {
return findClass(name);
}
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String name) {
// 从文件或网络加载字节码
return null;
}
}2. 线程上下文类加载器
SPI 机制使用,如 JDBC、JNDI。
// 设置线程上下文类加载器
Thread.currentThread().setContextClassLoader(classLoader);
// 获取线程上下文类加载器
ClassLoader loader = Thread.currentThread().getContextClassLoader();3. OSGi 模块化
每个模块有自己的类加载器,形成网状结构。
自定义类加载器
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(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 name) throws IOException {
String fileName = classPath + File.separator
+ name.replace(".", File.separator) + ".class";
try (FileInputStream fis = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
}
}
// 使用
MyClassLoader loader = new MyClassLoader("/path/to/classes");
Class<?> clazz = loader.loadClass("com.example.MyClass");
Object instance = clazz.newInstance();类的唯一性
同一个类被不同类加载器加载,会被视为不同的类。
MyClassLoader loader1 = new MyClassLoader("/path");
MyClassLoader loader2 = new MyClassLoader("/path");
Class<?> class1 = loader1.loadClass("com.example.Test");
Class<?> class2 = loader2.loadClass("com.example.Test");
System.out.println(class1 == class2); // false
System.out.println(class1.equals(class2)); // false类的卸载
类的卸载条件:
- 该类的所有实例都已被回收
- 加载该类的 ClassLoader 已被回收
- 该类的 Class 对象没有被引用
注意
Bootstrap、Extension、Application 加载器加载的类不会被卸载。