我们知道在 JVM 中类加载总共使用 5 步组成的,而类的生命周期总共有 7 个阶段,如下图所示:
其中每步的含义如下:
1.加载
加载(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载 Class Loading 是不同的,一个是加载 Loading 另一个是类加载 Class Loading,所以不要把二者搞混了。
在加载 Loading 阶段,Java 虚拟机需要完成以下 3 件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
2.验证
验证是连接阶段的第一步,这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,保证这些信 息被当作代码运行后不会危害虚拟机自身的安全。
验证选项:
- 文件格式验证
- 字节码验证
- 符号引用验证...
3.准备
准备阶段是正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段。
比如此时有这样一行代码:
public static int value = 123;
它是初始化 value 的 int 值为 0,而非 123。
4.解析
解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。
也就是说这个阶段会涉及到以下三个概念:
- 符号引用:类文件中的一种抽象引用方式,它并不涉及具体的内存地址或对象实例。符号引用包括了三个方面的信息:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。这些信息足够唯一地确定一个类、字段或者方法,但在类被加载到 JVM 之前,并没有与实际的内存布局关联。
- 直接引用:一种可以直接指向目标对象、类、字段或者方法在 JVM 内存中的物理位置的引用方式,例如指针、偏移量等。一旦有了直接引用,就可以直接访问目标实体,而无需再经过其他查找过程。
- 替换过程:当 JVM 在解析阶段需要对某个符号引用进行解析时,会根据类加载的结果生成对应的直接引用。比如,当一个类引用了另一个类的方法或字段时,解析阶段会确保被引用的目标类已经被加载,并计算出被引用方法或字段在内存中的准确位置,然后用这个位置信息替换掉原来的符号引用。
5.初始化
初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。初始化阶段就是执行类构造器方法的过程,当然初始化阶段也会执行静态初始化块和静态字段的初始化赋值的操作。
那么问题来了,以上步骤中在进行【解析】阶段时有两个比较难理解的定义【直接引用】和【符号引用】,那么如何通俗易懂的理解二者的概念呢?
符号引用 VS 直接引用
这里通俗易懂的理解一下符号引用和直接引用:
- 符号引用:想象一下你去图书馆找一本书,但你没有具体的书架位置,只有书名和作者,这是书名和作者就像是符号引用,你并不知道它在图书馆的哪个位置?你只知道书名和作者信息。
- 直接引用:之后你去了借阅台或者目录索引处查找这本书的具体位置,比如在第 3 层的 A 区 12 排 5 列,你可以直接走到这个位置找到书。这个具体的位置信息就像直接引用,它是一个可以直接定位到实体的指针或句柄。
也就是在【解析】步骤中,其实是将以字符串形式存在的,描述了类、接口、字段或方法的名称,以及可能包含的其他关于被引用项的信息,转换成实际内存对象的过程。直接引用是实际的内存地址或偏移量,使用它可以让 JVM 能够快速地访问对象、方法或字段。
本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:并发编程、MySQL、Redis、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、JVM、设计模式、消息队列等模块。