Java中的JVM类加载过程详解
字数 1411 2025-11-27 21:04:03
Java中的JVM类加载过程详解
一、类加载过程概述
类加载是JVM将类的字节码数据从不同数据源(如.class文件、网络、jar包等)加载到内存,并对数据进行校验、解析和初始化,最终形成可被JVM直接使用的Java类型的过程。整个过程分为加载、验证、准备、解析、初始化五个阶段。
二、加载阶段(Loading)
-
主要任务:通过类的全限定名获取定义此类的二进制字节流,并将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构,最后在堆内存中生成一个代表该类的java.lang.Class对象,作为方法区这些数据的访问入口。
-
具体步骤:
- 查找字节码:通过类加载器(ClassLoader)根据类的全限定名(如com.example.Test)定位到.class文件
- 读取字节码:将.class文件读取到内存中
- 创建Class对象:在堆中创建对应的Class对象,用于访问方法区中的类型数据
- 注意:数组类的创建比较特殊,由JVM直接生成,不通过类加载器
三、验证阶段(Verification)
- 目的:确保字节码文件符合JVM规范,不会危害JVM安全
- 验证内容:
- 文件格式验证:检查魔数(0xCAFEBABE)、版本号、常量池类型等
- 元数据验证:对类的元数据信息进行语义校验(如是否有父类、是否继承final类等)
- 字节码验证:通过数据流分析验证方法体的Code属性(如类型转换是否安全、跳转指令是否合理)
- 符号引用验证:在解析阶段发生,验证符号引用能否正确解析
四、准备阶段(Preparation)
- 任务:为类变量(static变量)分配内存并设置初始值
- 关键细节:
- 只分配类变量,不包含实例变量
- 初始值通常是数据类型的零值(如int为0,boolean为false,引用为null)
- 如果类变量被final修饰,准备阶段会直接赋值为代码中指定的值
五、解析阶段(Resolution)
- 任务:将常量池内的符号引用替换为直接引用
- 解析类型:
- 类或接口解析:将符号引用转换为具体的类或接口的直接引用
- 字段解析:解析字段属于哪个类或接口
- 方法解析:确定方法的直接引用
- 接口方法解析:类似方法解析,但针对接口
- 解析时机:可以在类加载完成后立即进行,也可以推迟到符号引用第一次被使用时(如invokedynamic指令)
六、初始化阶段(Initialization)
-
触发条件(满足任一):
- 创建类的实例(new、反射、反序列化)
- 调用类的静态方法
- 访问类或接口的静态字段(final常量除外)
- 使用反射调用类方法
- 初始化子类时父类未初始化
- 作为程序入口的类(包含main方法)
-
执行内容:
- 执行类构造器
<clinit>()方法,该方法由编译器自动收集:- 按源码顺序合并所有静态变量的赋值语句
- 合并静态代码块(static{}块)
- 保证在多线程环境下
<clinit>()方法被正确加锁同步
- 执行类构造器
七、类加载的时机特点
- 加载、验证、准备三个阶段按顺序开始(但不一定按顺序完成)
- 解析阶段可能在初始化之后开始(支持运行时绑定)
- 初始化阶段严格按顺序进行,是类加载的最后一步
- 每个类只会被加载一次,由类加载器保证唯一性
八、实际应用示例
public class Test {
static int value = 123; // 准备阶段value=0,初始化阶段赋值为123
static final int CONST = 456; // 准备阶段直接赋值为456
static {
System.out.println("静态代码块执行");
}
}
当首次使用Test类时,JVM会按顺序执行:加载→验证→准备(value=0, CONST=456)→解析→初始化(执行静态代码块和value=123的赋值)。