类加载过程详解
一、类的生命周期
类的生命周期分为以下七个阶段:
1. 加载(Loading)
类加载器通过查找并加载类的字节码文件(通常是 .class
文件)到内存中。加载过程会将类的二进制数据读入并转换成 JVM 中可操作的形式。
2. 验证(Verification)
加载的字节码会经过验证,确保其符合 JVM 的规范。这个阶段确保类的正确性,不会对 JVM 安全性和稳定性构成威胁。验证包括文件格式验证、字节码验证等。
3. 准备(Preparation)
准备阶段为类的静态变量分配内存,并设置默认值(如 0、null)。此时,类的实例变量不会被初始化,只有静态变量会被初始化。
4. 解析(Resolution)
解析阶段将常量池中的符号引用转换为直接引用。例如,将方法名、字段名等符号映射为内存中的实际方法地址和字段地址。
5. 初始化(Initialization)
在初始化阶段,类的静态变量会被赋予实际的值,静态代码块也会被执行。这个过程是类的初始化,通常在类首次被使用时发生。
6. 使用(Using)
类已经初始化完成并且可以被使用。对象可以被创建,方法可以被调用,类的功能可以正常运行。
7. 卸载(Unloading)
类被卸载出 JVM 的内存中,这通常发生在类加载器被回收时。类的卸载是由垃圾回收机制触发的,且并非所有类都能被卸载,只有某些条件满足时,类才会被回收。
每个阶段都是类从加载到最终被回收的关键部分,确保类在 JVM 内的正确运行和管理。
二、类加载过程
类加载过程
类的加载过程包括三个主要阶段:加载、连接和初始化。连接过程又分为验证、准备和解析。下面将详细讲解每个阶段的内容。
加载
类加载的第一步是获取类的二进制字节流并将其转换为 JVM 可以使用的格式。具体步骤如下:
- 通过类的全名获取定义该类的二进制字节流。
- 将字节流代表的静态存储结构转换为方法区的运行时数据结构。
- 在内存中生成一个
Class
对象,作为方法区数据的访问入口。
加载是通过类加载器完成的。类加载器是 JVM 中的一个关键组件,它负责从不同的来源加载类字节码,比如文件系统、JAR 包、网络等。类加载器的选择由 双亲委派模型 决定,但我们也可以自定义类加载器来控制类加载的过程。
验证
验证是连接过程中的第一步,目的是确保类的字节码符合 JVM 的规范,避免执行恶意代码。验证的阶段包括:
- 文件格式验证:检查类文件的二进制格式是否符合规定。
- 元数据验证:检查类的字节码语义是否符合要求。
- 字节码验证:确保程序的语义正确,不会出现无法识别的指令。
- 符号引用验证:在解析符号引用时检查类、字段、方法等的合法性。
验证是可选的,在某些情况下,如生产环境中,使用 -Xverify:none
可以关闭验证以提高性能,但这并不推荐。
准备
准备阶段为类的静态变量分配内存并设置默认值。注意:
- 类变量(静态变量)会在方法区内存中分配,而 实例变量 则在对象实例化时分配。
- 默认值一般是数据类型的零值(如 0、null、false),除非静态变量是
final
,此时其值会在准备阶段就被赋值。
解析
解析阶段将常量池中的符号引用替换为直接引用。符号引用是类、字段、方法等的描述信息,而直接引用是内存地址或偏移量。解析的目标是将符号引用转换为可以直接访问的内存地址。
初始化
初始化是类加载的最后一步,这一阶段会执行类的 <clinit>()
方法,这是 JVM 自动生成的静态初始化方法。<clinit>()
方法用于初始化类中的静态字段和执行静态代码块。初始化通常是在类首次被使用时触发的。
触发初始化的情况包括:
- 通过
new
、getstatic
、putstatic
或invokestatic
指令使用类。 - 使用反射(例如
Class.forName()
)时。 - 当类的父类未初始化时,初始化父类。
- 程序启动时,JVM 会初始化指定的主类。
- 使用
MethodHandle
和VarHandle
时。
通过这些触发条件,JVM 确保只有在需要使用类时才进行初始化。
三、类卸载
类卸载是指类的 Class
对象被垃圾回收器(GC)回收,从而释放相关资源。类的卸载并非自动发生,必须满足以下三个条件:
1. 类的所有实例对象已被 GC
类的实例对象必须都已被垃圾回收,即堆内存中不再存在该类的任何对象。否则,类的 Class
对象无法被卸载。
2. 类没有被其他任何地方引用
如果类的 Class
对象在其他地方被引用,类就无法被卸载。这意味着没有活跃的引用指向该类,包括其他类或类加载器。
3. 类加载器实例已被 GC
类的加载器必须也被垃圾回收。如果类加载器的实例仍然存在,类就无法被卸载。Java 自带的类加载器(如 BootstrapClassLoader
、ExtClassLoader
、AppClassLoader
)通常不会被卸载,因为它们在 JVM 生命周期内是常驻的。而自定义的类加载器则可能在没有引用时被回收,从而卸载加载的类。
关键点
- JVM 内置的类加载器加载的类一般不会被卸载,因为它们的加载器(如
BootstrapClassLoader
)是 JVM 的一部分,不会被 GC 回收。 - 自定义类加载器加载的类在类加载器被回收后,才可能被卸载。
因此,类卸载的实现和 JVM 的类加载器的生命周期紧密相关,只有当加载类的类加载器不再被使用并被 GC 回收时,才可能卸载类。