11.2 类加载的各个阶段
类加载是把新类型添加到运行中的 JVM 进程里的过程。这是新代码进入 Java 系统的唯一方式,也是 Java 平台中把数据变成代码的唯一方式。类加载分为几个阶段,下面一一介绍。
11.2.1 加载
类加载过程首先会加载一个字节数组。这个数组往往从文件系统中读取,不过也可以从 URL 或其他位置(一般使用 Path 对象表示)读取。
Classloader::defineClass() 方法的作用是把类文件(表示为字节数据)转换成类对象。这是受保护的方法,因此不通过子类无法访问。
defineClass() 的第一个任务是加载。加载的过程中会生成类对象的骨架,对应于尝试加载的类。这个阶段会对类做些基本检查(例如,会检查常量池中的常量,确保前后一致)。
不过,加载阶段不会生成完整的类对象,而且类也还不能使用。加载结束后,必须链接类。这一步细分为几个子阶段:
验证
准备和解析
初始化
11.2.2 验证
验证阶段确认类文件与预期相符,而且没有违背 JVM 的安全模型(详情参见 11.3 节)。
JVM 字节码经过精心设计,(几乎)可以静态检查。这么做会减慢类加载过程,不过能加快运行时(因为此时可以不做检查)。
验证阶段的目的是避免 JVM 执行可能导致自身崩溃的字节码,或者把 JVM 带入未测试的未知状态,出现恶意代码能攻击的漏洞。验证字节码能防御恶意编写的 Java 字节码,还能防止不信任的 Java 编译器输出无效的字节码。
默认方法机制在类加载过程中能正常运作。加载接口的实现时,会检查是否实现了默认方法,如果实现了,类加载过程正常向下运行;如果未实现,则为实现接口的类打补丁,添加缺失方法的默认实现。
11.2.3 准备和解析
验证通过后,类就做好了使用的准备。内存分配好了,类中的静态变量也准备初始化了。
在这个阶段,变量还未初始化,而且也没执行新类的字节码。开始运行代码之前,JVM 要确保运行时知道这个类文件引用的每个类型。如果不知道,可能还要加载这些类型——再开始其他类加载过程,让 JVM 加载新类型。
这个加载和发现的过程可能会不断进行下去,直到知道所有类型为止。这对最初加载的类型来说,叫作“传递闭包”。1
1和第 6 章一样,我们从数学的图论分支中借用了“传递闭包”这种说法。
下面看个简单的示例,我们来分析一下 java.lang.Object 类的依赖。图 11-1 显示的是简化的 Object 类依赖图,只显示了 Object 公开 API 可见的直接依赖,以及各个依赖的 API 可见的直接依赖。而且,反射子系统中 Class 类的依赖,以及 I/O 子系统中 PrintStream 和 PrintWriter 类的依赖也做了大量简化。
从图 11-1 可以看出 Object 类的部分传递闭包。

图 11-1:类型的传递闭包
11.2.4 初始化
解析阶段结束后,JVM 终于可以初始化类了。这个阶段会初始化静态变量,还会运行静态初始化代码块。
这是 JVM 首次执行新加载的类的字节码。静态初始化代码块运行完毕后,类就完全加载好,可以使用了。
