类的加载过程

Mr.LR2022年4月7日
大约 7 分钟

类的加载过程

1、Loading(装载)阶段

1、简言之

将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型——类模板对象。

2、装载操作

在加载类时,Java虚拟机必须完成以下3件事情:

  • 将class文件加载到内存
  • 将静态数据结构转化成方法区中运行时的数据结构
  • 在堆中生成一个代表这个类的 java.lang.Class对象作为数据访问的入口

类模板对象

所谓类模板对象,其实就是Java类在JVM内存中的一个快照,JVM将从字节码文件中解析出的常量池、类字段、类方法等信息存储到类模板中,这样JVM在运行期便能通过类模板而获取Java类中的任意信息,能够对Java类的成员变量进行遍历,也能进行Java方法的调用

反射的机制即基于这一基础。如果JVM没有将Java类的声明信息存储起来,则JVM在运行期也无法反射。

class实例的位置

类将.class文件加载至元空间后,会在堆中创建一个Java.lang.Class对象,用来封装类位于方法区内的数据结构,该Class对象是在加载类的过程中创建的,每个类都对应有一个Class类型的对象。(instanceKlass -->mirror :Class的实例)

image-20220408175415744

数组类的加载有什么不同

创建数组类的情况稍微有些特殊,因为数组类本身并不是由类加载器负责创建,而是由JVM在运行时根据需要而直接创建的,但数组的元素类型仍然需要依靠类加载器去创建。创建数组类(下述简称A)的过程:

  1. 如果数组的元素类型是引用类型,那么就遵循定义的加载过程递归加载和创建数组A的元素类型;
  2. JVM使用指定的元素类型和数组维度来创建新的数组类。
  3. 如果数组的元素类型是引用类型,数组类的可访问性就由元素类型的可访问性决定。否则数组类的可访问性将被缺省定义为public。

2、Linking(链接)阶段

1、链接阶段之Verification(验证)

目的:保证加载的字节码是合法、合理并符合规范的。确保加载的类符合 JVM 规范和安全。

例如验证字节码头文件标识是否正确

整体说明

验证的内容则涵盖了类数据信息的格式验证、语义检查、字节码验证,以及符号引用验证等。

  • 其中格式验证会和装载阶段一起执行。验证通过之后,类加载器才会成功将类的二进制数据信息加载到方法区中。
  • 格式验证之外的验证操作将会在方法区中进行。

2、链接阶段之Preparation(准备)

简言之

为static变量在方法区中分配内存空间,设置变量的默认值 (注意:准备阶段只设置类中的静态变量(方法区中),不包括实例变量(堆内存中),实例变量是对象初始化时赋值的)

Java虚拟机为各类型变量默认的初始值如表所示

类型默认初始值
byte(byte)0
short(short)0
int0
long0L
float0.0f
double0.0
char\u000
booleanfalse
referencenull

注意:Java并不支持boolean类型,对于boolean类型,内部实现是int,由于int的默认值是0,故对应的,boolean的默认值就是false。

这里不包含基本数据类型的字段用staticfinal修饰的情况,因为final在编译的时候就会分配了,准备阶段会显式赋值

// 一般情况:static final修饰的基本数据类型、字符串类型字面量会在准备阶段赋值
private static final String str = "Hello world";
// 特殊情况:static final修饰的引用类型不会在准备阶段赋值,而是在初始化阶段赋值
private static final String str = new String("Hello world");
  • 注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
  • 在这个阶段并不会像初始化阶段中那样会有初始化或者代码被执行

3、链接阶段之Resolution(解析)

简言之

虚拟机将常量池内的符号引用替换为直接引用的过程(符号引用比如我现在import java.util.ArrayList这就算符号引用,直接引用就是指针或者对象地址,注意引用对象一定是在内存进行)

暂时理解:找到类字段、方法的具体地址,从而使得方法被调用

具体描述

符号引用就是一些字面量的引用,和虚拟机的内部数据结构和和内存布局无关。比较容易理解的就是在Class类文件中,通过常量池进行了大量的符号引用。但是在程序实际运行时,只有符号引用是不够的,比如当如下println()方法被调用时,系统需要明确知道该方法的位置。

3、Initialization(初始化)阶段

简言之

为类的静态变量赋予正确的初始值。(显式初始化)

具体描述 类的初始化是类装载的最后一个阶段。如果前面的步骤都没有问题,那么表示类可以顺利装载到系统中。此时,类才会开始执行Java字节码。(即:到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。)

初始化阶段的重要工作是执行类的初始化方法:clinit方法。

  • 该方法仅能由Java编译器生成并由JVM调用,程序开发者无法自定义一个同名的方法,更无法直接在Java程序中调用该方法,虽然该方法也是由字节码指令所组成。
  • 它是由类静态成员的赋值语句以及static语句块合并产生的。

1、父类子类加载顺序

由父及子,静态先行。

2、不会生成clinit方法情况

  • 一个类中并没有声明任何的类变量,也没有静态代码块时
  • 一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时
  • 一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式

JVM初始化步骤

  • 假如这个类还没有被加载和连接,则程序先加载并连接该类
  • 假如该类的直接父类还没有被初始化,则先初始化其直接父类
  • 假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机:

只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

  • 创建类的实例,也就是new的方式
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如Class.forName("com.kkrot.jvm.Test"))
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

4、类的Using(使用)

开发人员可以在程序中访问和调用它的静态类成员信息(比如:静态字段、静态方法),或者使用new关键字为其创建对象实例。

5、类的Unloading(卸载)

Java虚拟机将结束生命周期的几种情况

  • 执行了System.exit()方法
  • 程序正常执行结束
  • 程序在执行过程中遇到了异常或错误而异常终止
  • 由于操作系统出现错误而导致Java虚拟机进程终止

参考

上次编辑于: 2023/2/14 22:01:12
贡献者: liurui-60837,liurui_60837,liurui