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

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

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

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

jvm_lift_1.png

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

重要的阶段

  • 装载:把二进制型式的 Java 类型读入 Java 虚拟机中
  • 连接:把已经读入虚拟机中的二进制型式的类型数据合并到虚拟机的运行时状态中去
    连接分三个子步奏:验证、准备和解析
  • 初始化:给类变量赋予适当的初始值
jvm_lift_2.png

1 装载

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

装载有三个基本动作组成

  • 通过该类型的完全限定名,产生一个代表该类型的二进制数据流;
  • 解析这个二进制数据流为方法区内的数据结构
  • 创建一个表示该类型的 java.lang.Class 类的实例

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

  • 从本地文件系统装载一个 Java Class 文件
  • 通过网络下载一个 Java Class 文件
  • 动态为某个类型计算其 Class 文件格式

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

2 连接

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

2.1 验证

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

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

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

验证扫描检测

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

  • 时间:第一趟扫描是在类被装载时进行的
  • 目的:它的主要目的是保证字节序列正确地定义一个类型,并且必须遵循 Java 的 class 文件的固定格式,这样它才能被编译成在方法区中的内部数据结构
  • 检验的内容:
    • 是否是魔数 OxCAFEBABE 开头
    • class 文件的主版本号和次版本号是否在虚拟机的支持范围之内
    • 。。。

第二趟扫描: 语义检查

  • 时间:在连接过程时进行
  • 目的:确保类型数据遵从 Java 编程语言的语义
  • 检验的内容:
    • 检查 class 文件每个组成部分,确保它们是否是其所属类型的实例,结构是否正确;
    • 检查 final 的类不能拥有子类;
    • 检查 final 的方法不能被覆盖;
    • 确保在类型和超类型直接没有不兼容的方法声明(比如两个方法拥有同样的名字,参数在数量、类型、类型上都相同,但是返回类型不同)
    • 检查这个类是否有父类(除了 Object 类以外的所有类,都必须有一个超类)

第三趟扫描: 字节码验证

  • 时间:在连接过程时进行
  • 目的:确保程序语义是合法的、符合逻辑的
  • 检验内容:对数据流和控制流分析

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

  • 时间:在动态连接阶段进行的
  • 目的:确保被引用的类、字段及其方法确实存在
  • 检查内容:
    • 符号引用中通过字符串描述的全限定名是否能找到对应的类
    • 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段
    • 符合引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问

符号引用验证的目的是确保解析动作能正常执行,如果没有通过验证、就会抛出 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 虚拟机实现必须在每个类或接口首次主动使用时初始化。

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

  • 当创建某个类的新实例时(或者通过字节码中执行 new 指令;或者通过不明确的创建、反射或者反序列化)
  • 当调用某个类或接口的静态字段,或者对该字段赋值(在字节码中,执行 getstatic 或 putstatic 指令时),用 final 修饰的静态字段除外,它被初始化编译时的常量表达式
  • 当调用 Java API 的某个反射方法时,比如类 Class 中的方法或者 java.lang.reflect 包中类方法
  • 当初始化某个类的子类时(某个类初始化时,要求它的超类已经被初始化了)
  • 当虚拟机启动时某个被标明为启动的类(即含有 main() 方法的那个类

例子:

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 虚拟机初始化一个类时,要求它所有父类都已经被初始化,但是这条规则不适用于接口

  • 在初始化一个类时,并不会初始化它所实现的接口
  • 在初始化一个接口时,并不会先初始化它的父接口

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339