03-JVM类加载机制你只多少?

2021-08-04  本文已影响0人  OpenCoder

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个部分统称为连接(Linking),这7个阶段的发生顺序如图:


image

加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始, 这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。

一.类的加载时机

思考:JVM在什么情况下会加载一个类呢?

通过以上的类加载流程,我们可以得知第一个环节就是加载一个类,因此当我们在IDEA中或直接运行某一个类的时候(比如First.java),其实是启动了JVM进程,然后JVM会通过类加载器将这个类的字节码(First.class)加载到内存,然后调用main方法开始执行。如果main方法中的代码是:

public class First {
    public static void main(String[] args) {
        //创建Second这个类的实例
        Second second = new Second();
    }
}

JVM这个时候会先检查内存中是否有该类的对象,如果没有会触发类加载器加载磁盘中的Second.class字节码到内存中,如下图:

image

二.类的加载阶段

“加载”是“类加载”(Class Loading)过程的一个阶段。在加载阶段,虚拟机需要完成以下3件事情:

(PS:方法区理解为JVM内存中的一块区域)

1)通过一个类的全限定名来获取定义此类的二进制字节流。

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:

3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

加载阶段结束后,Java虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中 了,(instanceKlass )
方法区中的数据存储格式完全由虚拟机实现自行定义,《Java虚拟机规范》未规定此区域的具体 数据结构。
类型数据妥善安置在方法区之后,会在Java堆内存中实例化一个java.lang.Class类的对象,

这个对象将作为程序访问方法区中的类型数据的外部接口。 加载阶段与连接阶段的部分动作(如一部分字节码文件格式验证动作)
是交叉进行的,加载阶段 尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的一部 分,
这两个阶段的开始时间仍然保持着固定的先后顺序。


image

三.类的连接阶段

连接阶段包括:验证、准备、初始化,对于这三个阶段没有太大的必要去深入研究里面的细节,这里的细节很多很繁琐,对于大部分同学来说重点理解其中的一些核心概念即可。
3.1验证阶段
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚 拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。 

主要包括四种验证,文件格式验证(魔数CAFEBABE),元数据验证,字节码验证,符号引用验证。

简单说就是我们的【.class】文件是否符合JVM规范,是否有被篡改,否则JVM是没法执行该字节码文件的。

[图片上传失败...(image-d69155-1628075278654)]_20210716174354.png)

3.2准备阶段

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段

测试代码:

public class Second {
    static int a;
    static int b = 10;
    static final int c = 20;
    static final String d = "hello";
    User user = new User();
}

反编译后的代码:

可见c和d均在准备阶段即完成赋值:

image

a、b、user在初始化阶段完成赋值:

image
3.3解析阶段

主要将常量池中的符号引用替换为直接引用的过程。

常量池的概念(提前了解)

常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)ff的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,主要包括下面几类常量:

Java代码在进行Javac编译的时候,在虚拟机加载Class 文件的时候进行动态连接。在Class文件中不会保存各个方法、字段最终 在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。当虚拟机做类加载时,将会从常量池获得对应的符号 引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

3.4小结

这三个阶段中,大家最应该关心的核心是:准备阶段

这个阶段是给加载进来的类进行空间的分配,以及static静态变量的空间分配,并且给与初始化值。

image
上一篇下一篇

猜你喜欢

热点阅读