Java 类的加载过程是由 类加载器(ClassLoader)控制的,涉及从类路径(或 JAR 文件)中加载类的字节码到 JVM 中。类加载不仅仅是简单的读取 .class 文件,还包括类的验证、准备、初始化等多个阶段。
下面将详细介绍 Java 类的加载顺序、过程和相关机制。
1. Java 类的加载过程
Java 类加载的过程大致分为以下几个步骤:
- 加载(Loading)
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
1. 加载(Loading)
加载阶段是指通过类加载器(ClassLoader)将类的字节码从文件系统或网络加载到 JVM 的内存中,并生成 Class 对象。
-
类加载器:负责在运行时根据类的全限定名(包括包名)将字节码文件加载到 JVM 中。
- 如果类已经加载过,JVM 不会重新加载。
- 加载的字节码文件存储在磁盘上(如
classes目录或 JAR 文件)或通过其他渠道(例如网络)传输。
-
类加载器的工作:
ClassLoader依据以下规则决定如何加载类:- Bootstrap ClassLoader:负责加载 JVM 核心库(
rt.jar)。 - Extension ClassLoader:加载 JDK 扩展库(
ext/目录下的类)。 - System ClassLoader(应用程序类加载器):加载系统类路径(
CLASSPATH)中的类。
- Bootstrap ClassLoader:负责加载 JVM 核心库(
2. 验证(Verification)
类加载后,JVM 会对加载的字节码进行验证,以确保它符合 JVM 的规范,且没有受到恶意代码或错误代码的影响。
验证阶段包括:
- 文件格式验证:确保文件是合法的
.class文件。 - 元数据验证:检查类的元数据,如字段、方法等,是否符合 Java 虚拟机规范。
- 字节码验证:确保字节码中的指令是合法的,并符合 JVM 运行时要求。
3. 准备(Preparation)
在这个阶段,JVM 会为类的静态变量分配内存,并将其初始化为默认值。此时,类中的静态变量尚未赋值,仅是内存的分配。
例如:
int类型的静态变量默认值为0。- 引用类型的静态变量默认值为
null。
这个阶段仅为静态变量分配内存,并未执行任何初始化代码。
4. 解析(Resolution)
解析阶段是将符号引用转换为直接引用的过程。在类文件中,可能会有一些指向其他类、方法、字段的符号引用。在解析阶段,JVM 会将这些符号引用解析为具体的内存地址或方法指针。
解析过程包括:
- 类解析:将类的符号引用解析为类的内存地址。
- 字段解析:将字段的符号引用解析为实际的内存地址。
- 方法解析:将方法的符号引用解析为实际的内存地址。
5. 初始化(Initialization)
初始化阶段是类加载过程的最后一个步骤。类的静态初始化会在此时发生,静态成员会被初始化,静态代码块会执行。
- 静态变量赋值:此时,静态变量会被赋予编译时指定的值,或者赋予默认值(如
0或null)。 - 静态代码块执行:如果类中定义了静态初始化块(
static {}),它会在类加载后、初始化之前执行。静态代码块只会执行一次。
2. Java 类加载顺序
Java 类加载的顺序涉及到类的引用、静态字段、父类的加载等。加载顺序遵循以下几个规则:
类首次引用时加载
类加载的触发通常由类的引用来决定,只有在程序首次引用该类时,JVM 才会加载类。类的引用可以通过以下方式发生:
- 使用
new关键字创建该类的对象。 - 调用类的静态方法或访问类的静态字段。
- 使用反射(如
Class.forName())加载类。 - 使用
instanceof运算符检查对象的类型。 - 通过反射或动态代理创建对象。
父类优先加载
如果一个类继承了父类,父类会优先加载。例如,当创建一个子类对象时,父类的静态初始化会首先执行,然后才是子类。
class A {
static {
System.out.println("A initialized");
}
}
class B extends A {
static {
System.out.println("B initialized");
}
}
public class Test {
public static void main(String[] args) {
new B(); // 输出 A 初始化,B 初始化
}
}
输出:
A initialized
B initialized
静态成员触发加载
当类有静态成员(如静态字段、静态方法、静态代码块)时,在第一次访问这些静态成员时,类会被加载并初始化。访问静态字段和方法会触发类加载,执行静态初始化。
class A {
static int x = 10; // 静态变量
}
public class Test {
public static void main(String[] args) {
System.out.println(A.x); // 访问静态字段会触发 A 类的加载
}
}
懒加载(Lazy Loading)
Java 类的加载是“懒加载”的,即类只有在第一次被需要时才会被加载。即使代码中有很多类声明,JVM 也不会在启动时加载所有的类,而是根据实际的需要加载。
父类的静态初始化
Java 类加载遵循“父类优先”的规则,父类的静态变量和静态代码块会在子类初始化之前先初始化。
- 在子类的静态初始化执行之前,父类的静态初始化会先执行一次。
初始化的时机
类的初始化发生在以下情况之一:
- 访问类的静态字段或调用静态方法。
- 创建类的实例。
- 使用
Class.forName("ClassName")手动加载类(会触发类的初始化)。
3. Java 类加载器
Java 中的类加载器(ClassLoader)负责将类从外部资源(如文件系统、网络)加载到 JVM 中。Java 采用 双亲委派模型 来实现类加载器机制。
- Bootstrap ClassLoader:加载核心库,如
rt.jar和 JVM 核心类。 - Extension ClassLoader:加载扩展库,如 JDK 的
ext目录下的类。 - System ClassLoader(应用程序类加载器):加载应用程序类路径下的类。
类加载器之间遵循父子关系,加载的过程是 双亲委派 机制:
- 子加载器(如
System ClassLoader)会先请求父加载器(如Extension ClassLoader)加载类,父加载器如果不能加载,就会由子加载器来加载。
双亲委派模型:
- 请求由子加载器发起,子加载器委托给父加载器。
- 如果父加载器可以加载,则由父加载器加载。
- 如果父加载器无法加载,子加载器再自己加载。
4. Java 类加载的顺序总结
Java 类加载的顺序通常遵循以下规律:
- 类首次被引用时加载:类加载是延迟的,只有在第一次使用时,类才会被加载。
- 父类优先加载:子类的初始化会在父类初始化之后进行。
- 静态成员触发加载:访问类的静态成员(字段、方法)会触发类的加载。
- 懒加载:类只有在首次使用时才会加载。
- 类加载器的父子关系:类加载器遵循父子加载器模型,父类加载器先尝试加载,子类加载器再尝试加载。
3187

被折叠的 条评论
为什么被折叠?



