<
Class对象初始化过程
>
上一篇

Touch事件分发时序
下一篇

理解ClassLoader加载机制

Class对象在执行引擎中的初始化过程

Class对象初始化过程

本次来总结学习下JVM加载class文件的具体过程

一个class文件被加载到内存中需要经过3大步:

1

1.装载

什么是装载

装载是指Java虚拟机查找.class文件并生成字节流,然后根据字节流创建java.lang.Class对象的过程

这一过程主要完成以下3件事:

  1. ClassLoader通过一个类的全限定名(包名+类名)来查找.class文件,并生成二进制字节流:其中class字节码文件的来源不一定是.class文件,也可以是jar包,zip包,甚至是来源于网络的字节流。
  2. 把.class文件的各个部分分别解析(parse)为JVM内部特定的数据结构,并存储在方法区。这里JVM会将这些.class文件的结构转化为JVM内部的运行时数据结构。这点和JSON解析过程有点类似。
  3. 在内存中创建一个java.lang.Class类型的对象。接下来程序在运行过程中所有对该类的访问都通过这个类对象,也就是这个Class类型的类对象事提供给外界访问该类的接口。

加载时机

一个项目经过编译后,往往会生成大量的.class文件。当程序运行时,JVM并不会一次性的将这些.class文件全部加载到内存中。JVM加载某.class文件时间并没有明确规定,不同的虚拟机有不同的实现。不过以下两种情况一般会对class进行装载操作

2.链接

链接的过程分为3步:验证,准备,解析

验证

验证是链接的第一步,目的是为了确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危及虚拟机本身的安全。主要包含以下几个方面的检验:

  1. 文件格式检验:检验字节流是否符合class文件格式的规范,并且能被当前版本的虚拟机处理
  2. 元数据检验:对字节码描述的信息进行语义分析,以保证其描述的内容符合Java语言规范的要求
  3. 字节码检验:通过数据流和控制流分析,确定程序语义是合法,符合逻辑的
  4. 符号引用检验:可以看作是对类自身以外(常量池中)的信息进行匹配性校验

准备

准备是链接的第2步,这一阶段的主要目的是为类中的静态变量分配内存,并为其设置“0值”,比如:

public static int value = 100;

在准备阶段,JVM会为value分配内存,并将其设为0.而真正的值100是在初始化阶段设置。并且此阶段进行内存分配的仅包括类变量,不包括实例变量(实例变量会在对象实例化时随着对象一起分配在Java堆中)

有一种特殊的情况:

public static final int value = 100;

静态常量,会在准备阶段就为value分配内存,并设置为100

解析

解析是链接的最后一步,这一阶段的任务是把常量池中的符号引用转化为直接引用(方法真正的内存地址),也就是具体的内存地址。在这一阶段,JVM会将常量池中的类,接口名,字段名,方法名等转化为具体的内存地址

对于符号引用和直接引用,就相当于微信好友列表中,保存的是好友的名称(符号引用),当我们真正给某个好友发消息时,计算机(JVM)会根据好友的名称找到对象计算机的IP地址(直接引用)并成功将消息发送给这一地址。

3.初始化

这是class加载的最后一步,这一阶段是执行类构造器 <clinit> 方法的过程,并真正初始化类变量,比如:

public static int value = 100;

在准备阶段value被分配内存并设置为0,在初始化阶段value就会被设置为100

初始化时机

对于装载阶段,JVM并没有规范何时具体执行。但是对于初始化阶段,主要有以下几种情况会触发class的初始化:

  1. 虚拟机启动时,初始化包含main方法的主类
  2. 遇到new指令创建对象实例时,如果目标对象类没有被初始化则进行初始化操作
  3. 当遇到访问静态方法或静态字段的指令时,如果目标对象类没有被初始化则进行初始化操作
  4. 子类的初始化过程如果发现其父类没有进行过初始化,则需要先触发其父类的初始化
  5. 使用反射API进行反射调用时,如果类没有进行过初始化则需要先触发其初始化
  6. 第一次调用java.lang.invoke.MethodHandle实例时,需要初始化MethodHandl指向方法所在的类

初始化类变量

在初始化阶段,只会初始化与类相关的静态赋值语句的静态语句,也就是有static关键字修饰的信息,而没有static修饰的语句块在实例化对象时才会执行

被动引用

上述的6种情况在JVM中被称为主动引用 ,除此6种情况之外所有引用类的方式都被称为被动引用被动引用并不会触发class的初始化

2

3

对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过子类Child来引用父类Parent种定义的静态字段,只会触发父类Parent的初始化而不会触发子类Child的初始化。

class初始化和对象的创建顺序

关于class初始化还有一点经常会在面试中被问到,那就是对象的初始化顺序。如下:

4

5

总结如下:

静态变量/静态代码块 -> 普通代码块 -> 构造函数

  1. 父类静态变量和静态代码块
  2. 子类静态变量和静态代码块
  3. 父类普通成员变量和普通代码块
  4. 父类的构造函数
  5. 子类普通成员变量和普通代码块
  6. 子类的构造函数

总结

主要学习了.class文件被加载到内存种所经过的过程,主要分为3大步装载,链接,初始化。其中链接又包括:验证,准备,解析,3小步

  1. 装载:指查找字节流,并根据此字节流创建类的过程。装载过程成功的标志就是在方法区种成功创建了类所对应的Class对象
  2. 链接:指验证创建的类,并将其解析到JVM中使之能被JVM执行
  3. 初始化:将标记为static的字段进行赋值,并且执行static标记的代码语句
Top
Foot