JVM 类加载机制详解
JVM(Java Virtual Machine,Java 虚拟机)中的类加载机制(Class Loading Mechanism)是指 JVM 在运行时动态加载 .class
文件,并将其转换为 JVM 识别的类对象(Class Object),以便执行。Java 的类加载采用按需加载(Lazy Loading)和双亲委派模型(Parent Delegation Model),确保类的安全性和避免重复加载。
1. 类加载的过程
Java 类的加载过程主要分为 五个阶段:
- 加载(Loading)
- 连接(Linking)
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
- 使用(Using)
- 卸载(Unloading)
(1)加载(Loading)
在类加载阶段,JVM 通过类加载器(ClassLoader)从字节码文件(.class
)或其他来源(如网络、JAR包等)读取二进制数据,转换成方法区(Method Area)中的类对象。
-
加载的来源:
- 本地
.class
文件 - Jar 包
- 网络(远程加载)
- 动态代理生成的类
- 其他自定义数据源
- 本地
-
主要任务:
- 通过 类加载器 读取
.class
文件,生成二进制字节流。 - 将字节流解析为 JVM 内部数据结构,存放在方法区。
- 在 堆区(Heap)中生成该类的 Class 对象,用于管理该类的元数据。
- 通过 类加载器 读取
示例:手动触发类加载
Class<?> clazz = Class.forName("com.example.MyClass"); // 反射触发类加载
(2)连接(Linking)
连接是把已经加载的类转换成可以运行的状态,包括三步:
- 验证(Verification):确保
.class
文件格式正确,符合 JVM 规范,避免恶意字节码。 - 准备(Preparation):为类变量(
static
变量)分配内存,并初始化默认值(不执行赋值操作)。 - 解析(Resolution):把类中的符号引用转换为直接引用(指向方法区中具体的内存地址)。
示例:准备阶段
public class Example {static int x = 10; // x 的默认值在准备阶段是 0,初始化阶段才会变为 10 }
(3)初始化(Initialization)
类初始化是执行静态代码的过程:
- 执行
static
变量的赋值 和 静态代码块(static {}
)。 - 初始化的顺序 按类的继承关系 从父类到子类 依次进行。
示例:类初始化
class Parent {static int a = 1;static { System.out.println("Parent 初始化"); } } class Child extends Parent {static int b = 2;static { System.out.println("Child 初始化"); } } public class Test {public static void main(String[] args) {System.out.println(Child.b);} }
输出:
Parent 初始化 Child 初始化 2
说明:
Parent
先初始化,因为Child
继承自Parent
。- 只有
static
变量和static
代码块才会在类初始化阶段执行。
(4)使用(Using)
类初始化完成后,就可以正常使用该类:
- 创建对象
- 调用静态方法
- 访问静态变量
(5)卸载(Unloading)
类在以下情况下会被卸载:
- 类的所有实例都被 GC
- ClassLoader 被 GC
- JVM 关闭
但是,JVM 不会卸载 Bootstrap ClassLoader 加载的类(即 rt.jar
内的核心类)。
2. Java 类加载器(ClassLoader)
类加载器负责将 .class
文件加载到 JVM。JVM 主要有三种类加载器:
类加载器 | 作用 | 负责加载的类 |
---|---|---|
Bootstrap ClassLoader | 启动类加载器 | Java 核心类库(rt.jar ) |
Extension ClassLoader | 扩展类加载器 | ext 目录下的 JAR |
Application ClassLoader | 应用类加载器 | classpath 下的类 |
示例:查看类加载器
System.out.println(String.class.getClassLoader()); // null (Bootstrap 加载) System.out.println(Test.class.getClassLoader()); // AppClassLoader
此外,Java 支持 自定义类加载器:
示例:自定义 ClassLoader
class MyClassLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] bytes = loadClassData(name); // 自定义加载逻辑return defineClass(name, bytes, 0, bytes.length);} }
3. 双亲委派机制(Parent Delegation Model)
工作原理
当一个 ClassLoader
需要加载类时,它不会直接加载,而是:
- 先委托给父类加载器。
- 父类加载失败(即找不到类)时,才会由子类加载器尝试加载。
作用
- 避免重复加载:防止 Java 核心类(如
java.lang.String
)被自定义类覆盖。 - 提高安全性:防止恶意代码篡改 Java 标准库。
示例:双亲委派
public class Test {public static void main(String[] args) {System.out.println(Test.class.getClassLoader()); // AppClassLoaderSystem.out.println(String.class.getClassLoader()); // null (Bootstrap)} }
4. 类的主动引用 & 被动引用
(1)主动引用(会触发类加载)
以下情况会触发类加载:
- 创建对象(
new
关键字) - 访问静态变量
- 调用静态方法
- 反射
- 子类初始化时,会先加载父类
示例:主动引用
class Parent {static { System.out.println("Parent 被加载"); } } public class Test {public static void main(String[] args) {Parent p = new Parent(); // 触发加载} }
(2)被动引用(不会触发类加载)
- 通过子类访问父类的静态变量
- 访问
final
常量 - Class.forName() 的
initialize=false
方式
示例:被动引用
class Parent {static { System.out.println("Parent 被加载"); }static int a = 10; } class Child extends Parent {} public class Test {public static void main(String[] args) {System.out.println(Child.a); // 仅加载 Parent} }
总结
- 类加载分为:加载、连接(验证、准备、解析)、初始化、使用、卸载。
- JVM 采用双亲委派机制,确保安全性和避免重复加载。
- 主动引用会触发类加载,被动引用不会。
JVM 类加载机制是 Java 运行时的核心之一,理解它有助于优化内存管理和类加载行为。