Java 虚拟机 (三) - 类的生命周期

2020-08-12  本文已影响0人  yxhuang

现在我们来讲述类的生命周期,这是 java 虚拟机系列文章的第三篇

Java 虚拟机通过装载、连接和初始化一个 Java 类型

类的完整生命周期包括:加载、验证、准备、解析、初始化、使用、卸载

jvm_lift_1.png

图 1-1 类的完整生命周期

重要的阶段

jvm_lift_2.png

1 装载

装载:就是把二进制型式的 Java 类型读入 Java 虚拟机中

装载有三个基本动作组成

类型二进制数据流产生的方式有多种
例如:

创建类型就是把一个类型的二进制数据解析为方法区中的内部数据结构,并在堆上建立一个 Class 对象的过程。

2 连接

连接:就是把已经读入虚拟机中的二进制型式的类型数据合并到虚拟机的运行时状态中去
连接分三个子步奏:验证、准备和解析

2.1 验证

验证的目的就是确认类型符合 Java 语言的语义,并且它不会危及虚拟机的完整性。
在验证阶段,虚拟机规范会说明在每种情况下应该抛出哪种异常,例如找不到相应的类,就会抛出 NoClassDefFoundError 异常。

在验证阶段,会使用 class 文件检验器保证装载的 class 文件内容是正确的结构。这里的 class 文件内容见 Java 虚拟机(二):Class 文件结构

class 文件检验器会进行四趟扫描检测;

验证扫描检测

第一趟扫描:class 文件的结构检查

第二趟扫描: 语义检查

第三趟扫描: 字节码验证

第四趟扫描: 符号引用的验证

符号引用验证的目的是确保解析动作能正常执行,如果没有通过验证、就会抛出 java.lang.InCompatibleClassChangeErro 的子类,例如 java.lang.ILLegalAccessError, java.lang.NoSuchFieldError, java.lang.NoSuchMethodError 等。

2.2 准备

在准备阶段,Java 虚拟机会为 类变量分配内存,设置默认值。

例如

 class A {
    public static int value = 123;
 }

value 是 A 的类变量,类型是 int, 在准备阶段,变量 value 赋值为默认值 0;至于将值 123 赋值给 value 是在初始化阶段。

基本类型的默认值

jvm_lift_3.png

2.3 解析(可选)

解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用的过程

常量池解析的最终目标是把符号引用替换为直接应用。

直接应用的数据格式:

实例变量的直接引用可能是从对象的映像开始算起到这个实例变量位置的偏移量;
实例方法的直接应用是到方法表的偏移量。

3 初始化

在初始化阶段是为类变量赋予正确的值。
这里的”正确“初始值指的是程序员希望这个类变量所具备的起始值。

在 Java 代码中,一个正确的初始值是通过类变量初始化语句或者静态初始化给出的。
拿上面的例子为例

 class A {
    public static int value = 123;
 }

value 在准备阶段已经赋予默认值 0, 在初始化阶段,就会设值为 123

3.1 初始化步骤

初始化一个类包含两个步骤:

3.2 主动使用和被动使用

这里的主动使用和被动使用,指的是虚拟机初始化 class 类时机时的使用方式,所有的 java 虚拟机实现必须在每个类或接口首次主动使用时初始化。

下面六种情形符合主动使用的要求:

例子:

public class TestParent {

    static int sleep = (int)(Math.random() * 3.0);

    static final  int touch = (int)(Math.random() * 2.0);

    static {
        System.out.println("TestParent was initialized");
    }
}

public class TestChild extends TestParent {

    static int crying = 1 + (int)(Math.random() * 2.0);

    static {
        System.out.println("TestChild was initialized.");
    }
}

public class TestClient {
    static {
        System.out.println("TestClient was initialized");
    }

    public static void main(String[] args){
        int hours = TestChild.sleep;
        System.out.println("TestClient hours: " + hours);
    }
}

输出:

TestClient was initialized
TestParent was initialized
TestClient hours: 1

从上面的例子可以看出, TestChild 没有被初始化,TestParent 被初始化了。

3.3 接口的初始化

当 Java 虚拟机初始化一个类时,要求它所有父类都已经被初始化,但是这条规则不适用于接口

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。

这是类的生命周期基本概况,后续文章会详细说明

上一篇 下一篇

猜你喜欢

热点阅读