一、类加载的五大核心步骤
Java 虚拟机的类加载过程分为五个步骤:加载、验证、准备、解析和初始化。
1. 加载(Loading)
- 任务:找到
.class文件,读取其二进制数据,并将其转换为 JVM 内部的运行时数据结构。 - 结果:在堆中创建一个代表该类的
java.lang.Class对象。
2. 验证(Verification)
- 任务:确保
.class文件的字节流符合 JVM 规范,并且不会危害 JVM 的安全。 - 关键检查:文件格式(魔数、版本)、元数据(类是否有父类)、字节码(方法操作是否合法)。
3. 准备(Preparation)
- 任务:为类的静态变量分配内存,并将其赋零值。
- 注意:
static final常量在编译时就已经确定,此阶段会直接赋其值。
4. 解析(Resolution)
- 任务:将常量池中的符号引用(如方法名、字段名)转换为直接引用(如内存地址)。
5. 初始化(Initialization)
- 任务:执行类中的
<clinit>()方法。<clinit>()方法由编译器自动生成,它包含了所有静态变量的赋值和静态代码块中的代码。 - 线程安全:JVM 保证
<clinit>()方法在多线程环境下是加锁执行的,以确保一个类只会被初始化一次。
二、类加载器:JVM的“层级化管理”
Java 提供了三层默认的类加载器,它们遵循双亲委派机制。
1. 启动类加载器(Bootstrap ClassLoader)
- 职责:加载 JVM 核心库(
rt.jar)。 - 特点:由 C++ 实现,无法在 Java 代码中直接获取。
2. 扩展类加载器(Extension ClassLoader)
- 职责:加载
JRE/lib/ext目录下的扩展库。
3. 应用类加载器(App ClassLoader)
- 职责:加载
classpath下的用户自定义类。
三、双亲委派机制:类加载的“安全”保障
-
工作流程:当一个类加载器收到加载请求时,它会先将请求委托给它的父加载器。父加载器再向上委托,直到顶层的启动类加载器。只有当父加载器无法加载时,子加载器才会尝试自己加载。
-
设计意义:
- 安全:防止用户伪造核心类(如
java.lang.String)。 - 避免重复:确保一个类只会被加载一次。
- 安全:防止用户伪造核心类(如
四、打破双亲委派机制:特殊场景的灵活处理
在某些特殊场景(如 Tomcat 的 Web 应用隔离、JDBC 的驱动加载),需要打破双亲委派机制。
Tomcat:Tomcat的WebAppClassLoader会优先加载 Web 应用目录下的类,而不是委托给父加载器。这使得不同的 Web 应用可以使用不同版本的依赖库,从而实现了类隔离。JDBC:JDBC 使用线程上下文类加载器(Thread Context ClassLoader) ,通过“反向委派”的方式,让DriverManager能够加载到厂商提供的驱动类。