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 { @Override protected 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()); // AppClassLoader System.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 运行时的核心之一,理解它有助于优化内存管理和类加载行为。