类加载过程的各个阶段(加载、验证、准备、解析、初始化)分别做了什么?

26 阅读4分钟

在 JVM 中,类加载过程是将类从其二进制表示形式(通常是 .class 文件)加载到内存中,并准备好供程序使用的过程。这个过程可以分为以下几个阶段:加载、验证、准备、解析和初始化。每个阶段都有其特定的任务和作用。

1. 加载(Loading)

任务

  • 查找并导入类的二进制数据。
  • 将这些二进制数据转换为 JVM 内部的数据结构(即 Class 对象)。
  • 在方法区中创建一个代表该类的 Class 对象。

细节

  • 通过类加载器(ClassLoader)来查找类文件并读取其内容。
  • 类加载器可以是系统提供的,也可以是用户自定义的。

2. 验证(Verification)

任务

  • 确保类的二进制数据符合 JVM 规范,没有非法的代码,保证字节码的正确性和安全性。

细节

  • 文件格式验证:验证字节流是否符合 Class 文件格式规范。
  • 元数据验证:验证类的元数据(如类的继承关系)是否符合 Java 语言规范。
  • 字节码验证:通过数据流和控制流分析,确保字节码不会危害 JVM 的安全。
  • 符号引用验证:确保常量池中的符号引用能被正确解析。

3. 准备(Preparation)

任务

  • 为类的静态变量分配内存,并将其初始化为默认值。

细节

  • 这一步并不包括静态变量的赋值操作,只是将其初始化为默认值(如 0、null 等)。
  • 例如,static int x = 5; 在准备阶段,x 仅被初始化为 0,赋值操作将在初始化阶段进行。

4. 解析(Resolution)

任务

  • 将常量池中的符号引用转换为直接引用。

细节

  • 符号引用是指以字符串形式描述的引用(如类名、方法名),而直接引用是指可以直接指向目标的指针或句柄。
  • 解析包括类或接口解析、字段解析、类方法解析、接口方法解析等。

5. 初始化(Initialization)

任务

  • 执行类的初始化代码,包括静态变量的赋值操作和静态代码块的执行。

细节

  • 这是类加载过程的最后一步,也是类被正式使用前的最后准备。
  • 如果类包含静态代码块或静态变量赋值操作,这些代码将在初始化阶段执行。
  • 初始化阶段是类加载过程中唯一会执行 Java 代码的阶段。

总结

类加载过程的各个阶段分别完成了以下任务:

  1. 加载(Loading):查找并导入类的二进制数据,创建 Class 对象。
  2. 验证(Verification):确保类的二进制数据符合 JVM 规范,保证安全性。
  3. 准备(Preparation):为类的静态变量分配内存,并初始化为默认值。
  4. 解析(Resolution):将符号引用转换为直接引用。
  5. 初始化(Initialization):执行类的初始化代码,包括静态变量赋值和静态代码块。

通过这些步骤,JVM 确保类在加载到内存中时是有效且安全的,并为其后续的使用做好了准备。

符号引用

符号引用(Symbolic Reference)是Java虚拟机中一种间接引用方式,它在编译时并不直接指向内存中的具体地址,而是通过符号来引用目标。符号引用在类加载和链接过程中会被解析为直接引用。

以下是一个简单的示例,展示了符号引用的概念:

假设有两个类:MainClassHelperClass

HelperClass.java

public class HelperClass {
    public void displayMessage() {
        System.out.println("Hello from HelperClass!");
    }
}

MainClass.java

public class MainClass {
    public static void main(String[] args) {
        HelperClass helper = new HelperClass();
        helper.displayMessage();
    }
}

在编译过程中,MainClass 中对 HelperClass 的引用是通过符号引用来实现的。具体来说:

  1. 符号引用

    • MainClass 的字节码中,对 HelperClass 类的引用和对 displayMessage 方法的引用都是通过符号引用来表示的。这些符号引用在编译时记录了类名、方法名和方法描述符等信息,但并没有具体的内存地址。
  2. 类加载和链接

    • 当 JVM 运行 MainClass 时,它会通过类加载器来加载 HelperClass
    • 在链接阶段,符号引用(如 HelperClass 类名和 displayMessage 方法名)会被解析为直接引用,即具体的内存地址或方法指针。

字节码示例

编译后的 MainClass 字节码(使用 javap -c MainClass 查看)可能类似于:

public static void main(java.lang.String[]);
  Code:
     0: new           #2                  // class HelperClass
     3: dup
     4: invokespecial #3                  // Method HelperClass."<init>":()V
     7: astore_1
     8: aload_1
     9: invokevirtual #4                  // Method HelperClass.displayMessage:()V
    12: return

在上面的字节码中,可以看到符号引用:

  • #2 是对 HelperClass 类的符号引用。
  • #3 是对 HelperClass 构造方法的符号引用。
  • #4 是对 displayMessage 方法的符号引用。

这些符号引用在运行时会被解析为具体的内存地址或方法指针,从而实现对 HelperClassdisplayMessage 方法的调用。

通过这个示例,可以看到符号引用如何在编译时表示类和方法的引用,并在运行时被解析为具体的直接引用。