Java 虚拟机加载 Java 类的过程

151 阅读3分钟

这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战

Java 是一门编译型语言,在完成代码的编写以后,需要使用 Java 编译器将源码编译成 .class 字节码文件,供 Java 虚拟机运行。Java 的源码和字节码都是以类为单位的,在字节码被 Java 虚拟机执行之前,需要将对应的类进行加载。

JVM 加载类的过程主要分为三个步骤:加载链接初始化

加载

加载的过程,就是查找 .class 文件的字节流并创建类的过程。字节流的来源是多样的,比如本地文件系统、JAR 文件、网络等。

这里需要提到 Java 的类加载器,启动类加载器是 Java 中最基础的类加载器,它没有对应的 Java 类,是用 C++ 实现的,负责加载最基础、重要的类,比如存放在 lib 目录下 JAR 包中的类。

其余的类加载器都是 java.lang.ClassLoader 的子类,有对应的 Java 类。由于它们也是一个 Java 类,因此也需要被加载,因此,除了启动类加载器之外,类加载器也都是通过其他的类加载器加载到 Java 虚拟机中的。

每当一个类加载器接收到一个加载请求时,它会先把要加载的类交给父类加载器,只有父类加载器找不到相应的类的情况下,当前的加载器才会尝试加载。这种机制也叫做双亲委派

链接

加载完成之后,会进入链接阶段。链接的过程,是将加载完成的类合并到 Java 虚拟机中,使其可以被执行的过程。大致可以分为验证、准备、解析三个阶段。

验证

验证的作用是确保一个 Java 类能够满足 Java 虚拟机的约束条件,通常情况下,Java 编译器生成的类文件都能满足这个条件。

准备

准备阶段的主要任务是为类的静态字段分配内存。

解析

解析阶段要处理的是将符号引用解析成实际引用。

解释一下:在类文件被加载到虚拟机之前,它不知道其他类、方法、字段的具体地址,连自己的方法和字段的地址也不知道,当需要这些引用时,Java 编译器会生成一个符号引用。但是在运行时,这些符号引用都需要能够定位到它代表的实际地址上。

这个工作就是解析阶段来完成的。如果解析的过程中,符号引用指向了一个未被加载的类,那么解析器就会出发这个类的加载过程。

顺便提一下,在 Java 虚拟机规范中,并没有要求虚拟机在链接过程中执行解析的工作,在虚拟机规范的要求中,这一步骤只需要在字节码被执行前完成就可以了。

初始化

最后就是初始化,类的初始化是线程安全的,并且只会被执行一次。

初始化这一步的工作有两个:为静态字段复制、执行 <cinit> 方法。

如果一个字段是基本类型或者字符串的常量,它会被编译器标记为一个常量值,由 Java 虚拟机直接初始化。除此之外的其他初始化,以及静态代码块中的代码,会被编译器一起放到名为 <cinit> 的初始化方法中。Java 虚拟机会通过枷锁的方式确保 <cinit> 方法只被执行一次。

类初始化会在很多情况下被触发,常见的包括以下几种:

  • 用户指定的主类会在启动时初始化。
  • 第一次用 new 命令创建一个类的实例时。
  • 访问一个类的静态方法或者静态字段时。
  • 一个类的子类需要被初始化时。
  • 通过反射机制是用一个类的时候。
  • 等等