我将以一个典型 Java 程序(HelloWorld)的执行过程为基础,逐步分析类加载的每一步,明确涉及的类、方法、输入数据格式、中间数据处理流程,以及最终输出数据格式。
本文从程序启动开始,涵盖类加载的所有阶段(加载、链接、初始化),并具体到每个阶段调用的类和方法。
类加载是 Java 虚拟机(JVM)将类文件(.class
文件或字节码)加载到内存,并将其转换为可执行的 java.lang.Class
对象的过程。它是 Java 程序运行的基础,确保程序所需的类在需要时被正确加载、验证和初始化。
Class
对象。假设我们运行一个简单的 Java 程序 Main.java
:
public class Main {
static int staticVar = 42;
static {
System.out.println("主程序中的静态块");
}
public static void main(String[] args) {
System.out.println("Hello, World!");
StaticTest test= new StaticTest();
}
}
class StaticTest {
static {
System.out.println("在StaticTest类中的静态块");
}
}
我们将以这个程序的执行过程,详细描述类加载的完整流程。
以下是类加载的详细步骤,涵盖从程序启动到类加载完成的每一步,明确涉及的类、方法、输入输出数据格式,以及中间处理流程。
java Main
命令。java.lang.BootClassLoader
(引导类加载器,JVM 内部)。BootClassLoader
)。BootClassLoader
加载核心类库(如 java.lang.Object
, java.lang.String
, java.lang.System
)。java.lang.System
的 initializeSystemClass()
方法,设置系统属性和环境。java Main
。JAVA_HOME/lib
或模块系统中的 java.base
)。Main
。BootClassLoader
读取核心类文件的字节码(.class
文件或模块化镜像)。java.lang.Class
对象。Class
对象,存储在方法区。类比:图书馆开门,主管理员(JVM)先把核心书籍(如字典、工具书)放到桌上(内存),准备好借阅系统。
Main
Main
类的 main
方法。java.lang.AppClassLoader
(应用类加载器)。java.lang.ClassLoader
(抽象类,提供加载逻辑)。jdk.internal.loader.BuiltinClassLoader
(AppClassLoader
的父类)。jdk.internal.loader.URLClassPath
(辅助类,管理类路径)。AppClassLoader
查找 Main
类。AppClassLoader
调用父类 ClassLoader
的 loadClass(String name)
方法。loadClass
实现双亲委派模型:
getParent()
获取父加载器(PlatformClassLoader
)。PlatformClassLoader
再委托其父加载器(BootClassLoader
)。AppClassLoader
调用自己的 findClass(String name)
方法。AppClassLoader
通过 BuiltinClassLoader
的 findClassInModuleOrClassPath
方法查找 Main.class
。URLClassPath
从类路径(-cp
或默认路径)读取 Main.class
的字节码。Class
对象:
AppClassLoader
调用 ClassLoader
的 defineClass(String name, byte[] b, int off, int len)
方法。java.lang.Class
对象,存储在方法区。Main
(全限定名 Main
)。Main.class
文件的二进制数据(字节数组)。BootClassLoader
检查是否为核心类(不是,失败)。PlatformClassLoader
检查是否为平台类(不是,失败)。AppClassLoader
从类路径找到 Main.class
。URLClassPath
打开文件流,读取 Main.class
的二进制数据。Class
实例,记录类的元信息(如方法表、字段表)。java.lang.Class
对象,表示 Main
类。类比:管理员(AppClassLoader
)在图书馆(类路径)找到 Main
这本书(.class
文件),检查是否已在核心书库或分馆(父加载器),然后把书的内容(字节码)整理成一本可用的书(Class
对象)。
Main
链接阶段将 Main
类的 Class
对象准备好,分为验证、准备和解析三个子阶段。
Main
类加载后,JVM 自动验证字节码。java.lang.ClassLoader
(提供上下文)。Main
类的字节码是否合法。CAFEBABE
、版本号等)。Main
类的字节码(存储在方法区)。java.lang.VerifyError
。Class
对象,标记为“可继续链接”。类比:管理员检查 Main
这本书是否完整(格式正确)、内容合法(没有危险指令),确保它可以安全借阅。
java.lang.Class
(存储静态变量信息)。Main
类的静态变量分配内存,并设置默认值。static int staticVar = 42;
的 staticVar
被分配内存,默认值为 0
(int 的默认值)。Main
类的 Class
对象,包含静态变量的元信息。staticVar
分配 4 字节(int 类型)。0
,暂不执行赋值语句(42
在初始化阶段赋值)。Class
对象,静态变量内存分配完成,默认值设置。类比:管理员为 Main
书的笔记页(静态变量)分配空白空间,暂时填上默认内容(0
)。
Main
类的符号引用(可选,延迟解析可能在初始化或运行时进行)。java.lang.ClassLoader
(解析引用时可能调用父加载器)。java.lang.Class
(存储常量池)。Main
类常量池中的符号引用,转换为直接引用。Main
类的 main
方法调用 System.out.println
,涉及 java.lang.System
和 java.io.PrintStream
。AppClassLoader
加载 System
和 PrintStream
类(如果尚未加载)。Main
类的常量池,包含符号引用(如 CONSTANT_Class_info
指向 java.lang.System
)。System
),调用 ClassLoader.loadClass("java.lang.System")
。BootClassLoader
加载 System
类,生成 Class
对象。#2
)被替换为 Class
对象的内存地址。Class
对象,常量池中的符号引用更新为直接引用。类比:管理员把 Main
书中的参考书目(符号引用)替换为具体书的地址(直接引用),确保可以快速找到其他书。
Main
Main.main
方法前,初始化 Main
类。java.lang.Class
(存储静态代码)。Main
类的静态初始化代码:
staticVar = 42;
System.out.println("主程序中的静态块");
方法(类初始化方法,由编译器生成)。Main
类的 Class
对象,包含
方法。
方法:
staticVar = 42
。System.out.println
,输出 "主程序中的静态块"
。System
),可能触发它们的加载和初始化。Main
类的静态变量初始化完成(staticVar = 42
)。Class
对象标记为“已初始化”。类比:管理员正式填写 Main
书的笔记页(静态变量和静态块),完成书的准备工作。
main
方法:触发其他类加载Main.main(String[] args)
。java.lang.AppClassLoader
(加载 StaticTest
类)。java.lang.ClassLoader
(提供加载逻辑)。jdk.internal.loader.BuiltinClassLoader
。jdk.internal.loader.URLClassPath
。java.lang.Class
(表示 StaticTest
类)。main
方法:
"Hello, World!"
(调用 System.out.println
)。StaticTest
实例:new StaticTest()
。StaticTest
类:
new StaticTest()
触发 StaticTest
类的加载。AppClassLoader
调用 loadClass("StaticTest")
。Main
类,URLClassPath
读取 StaticTest.class
的字节码。defineClass
创建 StaticTest
的 Class
对象。StaticTest
类:
StaticTest
字节码的合法性。StaticTest
的静态变量分配内存。StaticTest
的符号引用(如 System
)。StaticTest
类:
方法,运行静态块:System.out.println("在StaticTest类中的静态块");
StaticTest
实例:
StaticTest
的构造方法,分配实例内存,生成对象。StaticTest
。StaticTest.class
文件的二进制数据。AppClassLoader
通过双亲委派查找 StaticTest
,从类路径加载字节码。"在StaticTest类中的静态块"
。StaticTest
类的 Class
对象(方法区)。StaticTest
实例(堆内存)。"在StaticTest类中的静态块"
。类比:程序运行时,管理员发现需要 StaticTest
这本书,重复“找书 -> 检查 -> 准备 -> 初始化”的过程,最终借出书并创建一本新副本(实例)。
运行 java Main
的完整输出:
主程序中的静态块
Hello, World!
在StaticTest类中的静态块
Main
类的静态块在初始化时执行。main
方法输出 "Hello, World!"
。StaticTest
类的静态块在 new StaticTest()
时执行。以下是类加载流程中涉及的所有类和关键方法:
阶段 | 类 | 方法 | 作用 |
---|---|---|---|
启动 | JVM 内部实现, BootClassLoader |
(无 Java 方法,由 JVM 调用) | 初始化 JVM,加载核心类库(如 java.lang.System )。 |
加载 | AppClassLoader , ClassLoader , BuiltinClassLoader , URLClassPath |
loadClass(String) , findClass(String) , defineClass(String, byte[], int, int) , URLClassPath.getResource() |
查找并读取 .class 文件,生成 Class 对象。 |
验证 | JVM 内部, ClassLoader |
(无直接 Java 方法,JVM 验证器) | 检查字节码合法性。 |
准备 | JVM 内部, Class |
(无直接 Java 方法,JVM 分配内存) | 为静态变量分配内存,设置默认值。 |
解析 | ClassLoader , Class |
loadClass(String) (间接调用) |
将符号引用转换为直接引用。 |
初始化 | Class , JVM 内部 |
(由 JVM 执行) |
执行静态变量赋值和静态块。 |
运行 | AppClassLoader , ClassLoader , Class |
loadClass(String) , defineClass , (构造方法) |
加载依赖类(如 StaticTest ),创建实例。 |
阶段 | 输入数据格式 | 中间处理 | 输出数据格式 |
---|---|---|---|
加载 | 类名(String ),.class 文件(字节数组) |
双亲委派查找,读取字节码,解析元信息,生成 Class 对象 |
java.lang.Class 对象(方法区) |
验证 | 字节码(Class 对象的元信息) |
检查文件格式、语义、字节码指令、符号引用 | 验证通过的 Class 对象 |
准备 | Class 对象(静态变量元信息) |
分配内存,设置默认值(如 int 为 0 ) |
Class 对象(静态变量内存分配完成) |
解析 | 常量池(符号引用) | 加载依赖类,替换符号引用为直接引用(内存地址) | Class 对象(常量池更新) |
初始化 | Class 对象( 方法) |
执行静态变量赋值和静态块,可能触发其他类加载 | Class 对象(静态变量初始化完成),控制台输出 |
运行 | 类名,字节码,构造参数 | 加载依赖类,执行构造方法,分配堆内存 | Class 对象,实例对象(堆),控制台输出 |
AppClassLoader
)先委托父加载器(如 PlatformClassLoader
, BootClassLoader
),只有父加载器失败时才自己加载。ClassLoader.loadClass
和 findClass
。PlatformClassLoader
和 AppClassLoader
支持模块系统。java.lang.Module
定义模块边界,影响类加载的可见性。Main
和 StaticTest
可能属于同一模块(如 unnamed module
)。类加载的完整流程从 JVM 启动到程序运行,涉及以下关键步骤:
BootClassLoader
加载核心类。AppClassLoader
通过 loadClass
和 defineClass
加载 Main
类,生成 Class
对象。Main
的
方法,初始化静态变量和块。main
方法,触发 StaticTest
类的加载、链接、初始化和实例化。涉及的核心类:ClassLoader
, AppClassLoader
, PlatformClassLoader
, BootClassLoader
, BuiltinClassLoader
, URLClassPath
, Class
, Module
。
数据流:从类名和字节码文件开始,经过加载、验证、准备、解析、初始化,最终生成 Class
对象和实例,输出到控制台。