目录
[阶段 1:加载(Loading)------"读取字节码,生成 Class 对象"](#阶段 1:加载(Loading)——“读取字节码,生成 Class 对象”)
[阶段 2:链接(Linking)------"验证 + 准备 + 解析,保证类的合法性"](#阶段 2:链接(Linking)——“验证 + 准备 + 解析,保证类的合法性”)
[子阶段 2.1:验证(Verification)------"校验字节码的合法性"](#子阶段 2.1:验证(Verification)——“校验字节码的合法性”)
[子阶段 2.2:准备(Preparation)------"为静态变量分配内存并赋默认值"](#子阶段 2.2:准备(Preparation)——“为静态变量分配内存并赋默认值”)
[子阶段 2.3:解析(Resolution)------"将符号引用转换为直接引用"](#子阶段 2.3:解析(Resolution)——“将符号引用转换为直接引用”)
[阶段 3:初始化(Initialization)------"执行静态代码,赋静态变量最终值"](#阶段 3:初始化(Initialization)——“执行静态代码,赋静态变量最终值”)
[1. 类加载器的分类(JDK8 为例)](#1. 类加载器的分类(JDK8 为例))
[2. 双亲委派模型的工作逻辑](#2. 双亲委派模型的工作逻辑)
[四、类加载子系统与 JVM 运行时数据区的关联](#四、类加载子系统与 JVM 运行时数据区的关联)
类加载子系统的核心作用是将编译后的.class 字节码文件加载到 JVM 内存中,并转换为方法区的类元数据,同时创建堆中的 Class 对象,为后续字节码执行提供基础;其工作遵循 "加载 - 链接 - 初始化" 的完整流程,且通过类加载器的双亲委派模型保证类加载的安全性和唯一性。
一、类加载子系统的核心作用
类加载子系统是 JVM 启动后第一个处理 Java 类的核心模块,所有 Java 类(包括自定义类、JDK 核心类)都必须经过它的处理才能被 JVM 执行,核心作用可总结为 3 点:
- 加载字节码文件 :从磁盘、网络、内存等数据源读取
.class文件(字节码),将其转换为 JVM 可识别的二进制流; - 构建类元数据 :将字节码中的类信息(类名、父类、接口、字段、方法、常量等)解析后存储到方法区,形成该类的元数据;
- 创建 Class 对象 :在堆 中创建对应类的
java.lang.Class对象(作为方法区类元数据的访问入口),后续通过该对象实现反射、实例化等操作。
补充:类加载子系统仅负责 "加载类",不负责 "执行类"(执行由 JVM 执行引擎完成),且加载后的类元数据会永久存储在方法区(JDK1.8 + 为元空间),直到类卸载时才释放。
二、类加载子系统的工作流程
类加载的完整流程分为 3 个阶段,其中 "链接" 又细分为 3 个子阶段,整体流程严格按顺序执行(个别子阶段可交叉执行,但核心顺序不变):
阶段 1:加载(Loading)------"读取字节码,生成 Class 对象"
这是类加载的第一步,核心是 "找到并加载类的字节码",具体做 3 件事:
- 获取字节码流 :类加载器根据类的全限定名(如
java.lang.String),从指定数据源(磁盘classpath、JAR 包、网络、动态生成等)读取.class文件的二进制字节流; - 转换存储格式 :将字节流转换为 JVM 方法区的类元数据结构(存储类的版本、字段、方法、常量池等信息);
- 创建 Class 对象 :在堆中生成一个代表该类的
java.lang.Class对象,作为访问方法区类元数据的 "唯一入口"(后续通过Class.forName()、对象getClass()获取的都是这个对象)。
关键:加载阶段的核心是类加载器(如 Bootstrap、Extension、Application、自定义加载器)的工作,不同类加载器负责加载不同来源的类(如 Bootstrap 加载 JRE/lib 核心类)。
阶段 2:链接(Linking)------"验证 + 准备 + 解析,保证类的合法性"
链接阶段是对加载后的类进行 "校验和预处理",确保类符合 JVM 规范,可安全执行,分为 3 个子阶段:
子阶段 2.1:验证(Verification)------"校验字节码的合法性"
这是 JVM 安全的重要保障,核心是检查字节码是否符合 JVM 规范,避免恶意或错误的字节码导致 JVM 崩溃,主要校验:
- 文件格式验证:检查字节流是否符合 Class 文件格式规范(如魔数
0xCAFEBABE、版本号是否兼容当前 JVM); - 元数据验证:检查类的元数据(如是否继承了
final类、方法参数类型是否合法、是否有重复的字段 / 方法); - 字节码验证:检查方法的字节码指令是否合法(如指令操作数类型匹配、跳转地址有效);
- 符号引用验证:检查类的符号引用(如引用的类 / 方法 / 字段是否存在)。
注:若验证失败,会抛出
VerifyError异常(如自定义一个不符合规范的 Class 文件,加载时就会触发)。
子阶段 2.2:准备(Preparation)------"为静态变量分配内存并赋默认值"
核心是 "初始化类的静态变量(类变量)",具体规则:
- 为方法区中类的静态变量(static 修饰) 分配内存空间;
- 将静态变量初始化为默认值 (而非代码中定义的初始值):
- 基本类型(int、boolean 等):默认值为 0、false 等;
- 引用类型:默认值为
null; - 示例:代码中定义
static int a = 10;,准备阶段会为a分配内存,赋值为 0(10 会在 "初始化" 阶段赋值)。
关键:仅处理静态变量 ,实例变量(非 static)不处理(实例变量在对象实例化时分配到堆中);
static final常量除外(编译期已确定值,准备阶段直接赋最终值,如static final int b = 20;,准备阶段赋值 20)。
子阶段 2.3:解析(Resolution)------"将符号引用转换为直接引用"
核心是 "解析类的符号引用(字符串形式)为直接引用(内存地址)":
- 符号引用:Class 文件中用字符串描述的类、方法、字段的引用(如
java.lang.Object、add(int)); - 直接引用:指向方法区类元数据的内存地址或偏移量(可直接访问的引用);
- 示例:类中调用
Object obj = new Object();,解析阶段会将 "java.lang.Object" 这个符号引用,转换为方法区中Object类元数据的直接内存地址。
注:解析阶段可延迟到 "首次使用该引用时" 执行(如首次调用方法时才解析),而非必须在链接阶段完成,目的是提升类加载效率。
阶段 3:初始化(Initialization)------"执行静态代码,赋静态变量最终值"
这是类加载的最后一步,也是唯一会执行 Java 代码(字节码)的阶段,核心是 "执行类的初始化逻辑",触发条件是:
- 首次创建类的实例(
new XXX()); - 首次调用类的静态方法 / 访问静态变量(除
static final常量); - 通过反射访问类(
Class.forName("com.example.Test")); - 初始化子类时,先初始化父类;
- JVM 启动时,初始化包含
main()方法的主类。
初始化阶段的具体操作:
- 执行类的静态代码块(static {});
- 为静态变量赋代码中定义的最终值 (如
static int a = 10;,此时将 a 从 0 改为 10); - 按 "静态变量声明顺序、静态代码块书写顺序" 执行(静态代码块可访问前面声明的静态变量,不可访问后面的)。
关键:初始化阶段仅执行一次(JVM 保证类的初始化是线程安全的),若初始化过程中抛出异常,该类会标记为 "初始化失败",后续无法再使用。
三、类加载子系统的核心支撑:类加载器与双亲委派模型
类加载子系统的 "加载" 阶段由类加载器完成,而类加载器遵循的双亲委派模型是保证类加载安全和唯一性的核心:
1. 类加载器的分类(JDK8 为例)
表格
| 类加载器类型 | 核心作用 | 加载来源 |
|---|---|---|
| 启动类加载器(Bootstrap) | 加载 JVM 核心类 | JRE/lib/rt.jar、核心类库(如 java.lang.*) |
| 扩展类加载器(Extension) | 加载 JVM 扩展类 | JRE/lib/ext/*.jar |
| 应用类加载器(Application) | 加载应用程序类 | classpath 下的类、JAR 包 |
| 自定义类加载器 | 加载自定义来源的类(如 Tomcat 的 WebappClassLoader) | 自定义路径(如 WAR 包、网络) |
2. 双亲委派模型的工作逻辑
类加载器加载类时,会先委托父加载器加载,父加载器加载不到时才自己加载,流程:
- 应用类加载器收到加载请求,先委托给扩展类加载器;
- 扩展类加载器再委托给启动类加载器;
- 启动类加载器尝试加载:若能加载则返回 Class 对象,若加载不到(如不是核心类),返回给扩展类加载器;
- 扩展类加载器尝试加载:若能加载则返回,否则返回给应用类加载器;
- 应用类加载器最后尝试加载:若仍加载不到,抛出
ClassNotFoundException。
核心价值:避免类重复加载(如
java.lang.String只能由启动类加载器加载),防止恶意类篡改核心类(如自定义java.lang.String会被双亲委派模型拦截,无法加载)。
四、类加载子系统与 JVM 运行时数据区的关联
类加载子系统的工作全程依赖 JVM 运行时数据区,核心关联:
- 加载阶段:字节码转换的类元数据存储到方法区 ,Class 对象创建到堆;
- 链接阶段:准备阶段为静态变量分配的内存位于方法区;
- 初始化阶段:执行静态代码块的临时数据存储到Java 虚拟机栈(栈帧的操作数栈、局部变量表);
- 类加载完成后,执行引擎通过堆中的 Class 对象访问方法区的类元数据,调用方法时在 Java 虚拟机栈创建栈帧,实例化对象时在堆分配内存。
总结
核心作用
- 读取.class 字节码文件,将类元数据加载到方法区,为执行引擎提供可执行的类信息;
- 创建堆中的 Class 对象,作为访问类元数据的入口,支撑反射、实例化等操作;
- 通过验证、双亲委派模型保证类加载的合法性和安全性。
核心工作流程
- 加载:读取字节码,生成 Class 对象(方法区存元数据,堆存 Class 对象);
- 链接:验证(校验合法性)→ 准备(静态变量赋默认值)→ 解析(符号引用转直接引用);
- 初始化:执行静态代码块,为静态变量赋最终值(仅首次使用时执行)。
关键特征
- 类加载是 "懒加载" 的(仅首次使用时触发),而非 JVM 启动时全部加载;
- 初始化阶段是唯一执行 Java 代码的阶段,且线程安全;
- 双亲委派模型保证核心类不被篡改,避免类重复加载。