类的加载过程简介
类的加载过程一般分为三个阶段,分别是 加载阶段、连接阶段 和 初始化阶段。如下图:
- 加载阶段:主要负责查找并且加载类的二进制数据文件,其实就是 class 文件。
- 连接阶段:细分如下三个阶段。
- 验证:主要是确保类文件的正确性,比如 class 的版本,class 文件的魔术因子是否正确等。
- 准备:把类的静态变量分配内存,并且为其初始化默认值。
- 解析:把类中的符号引用转换为直接引用。
- 初始化阶段:为类的静态变量赋予正确的初始值(代码编写阶段给定的值)。
类的主动使用和被动使用
主动使用 会使类初始化
- 通过 new 关键字会导致类的初始化。
- 访问类的静态变量,包括读取和更新会导致类的初始化。
- 访问类的静态方法,会导致类的初始化。
- 对某个类进行反射操作,会导致类的初始化。
- 初始化子类会导致父类的初始化。
- 启动类
其中有,如果通过子类调用父类静态变量,不会使子类初始化。
被动使用 不会使类初始化
- 构造某个类的数组时并不会导致该类的初始化。
- 引用类的静态常量不会导致类的初始化。
其中,如果类的静态常量不是一个常数,而是通过一个对象获得的常数,则该类还是会被初始化。因其在加载、连接阶段是无法对其进行计算的。
类的加载过程
类的加载阶段
类的加载就是将 class 文件中的二进制数据读取到内存之中,然后将该字节流所代表的的静态存储结构转换为方法区中运行时的数据结构,并且在堆内存中生成一个该类的 java.lang.Class
对象,作为访问方法区数据结构的入口。如下图:
类的链接阶段
类的链接阶段细分为三个小的过程,分别是 验证、准备 和 解析。
-
验证
-
验证主要目的是确保 class 文件的字节流所包含的内容符合当前 JVM 的规范要求,并且不会出现危害 JVM 自身安全的代码,当字节流的信息不符合要求时,则会抛出
VerifyError
这样的异常或者是其子异常。验证信息有:验证文件格式、元数据的验证、字节码验证 和 符号引用验证。
准备
- 当一个 class 的字节流通过了所有的验证过程之后,就开始为该对象的类变量,也就是静态变量,分配内存并且设置初始值了,类变量的内存会被分配到方法区中,不同于实例变量会被分配到堆内存中。 解析
-
所谓解析就是在常量池中寻找类、接口、字段和方法的符号引用,并且将这些符号引用替换成直接引用的过程。
解析过程主要是针对类接口、字段、类方法和接口方法这四类进行的。
类的初始化阶段
初始化阶段做的最主要的一件事情就是执行 <clinit>()
方法的过程,在 <clinit>()
方法中所有的类变量都会被赋予正确的值,也就是在程序编写的时候指定的值。
<clinit>()
方法是在编译阶段生成的,也就是它已经包含在了 class 文件中了,<clinit>()
中包含了所有类变量的赋值动作和静态语句块的执行代码,编译器收集的顺序是由执行语句在源文件中的出现顺序所决定的。静态语句块只能对后面的静态变量进行赋值,但是不能对其进行访问。