JAVA后端面试必会之JVM虚拟机类加载机制

.class字节码文件
使用javac命令编译后的.class文件,就是java的字节码文件。

平时使用开发工具如Intellij idea中看到的.class文件都是经过反编译后的java代码,实际上.class文件都是一组以8位字节为基础单位的二进制流,用sublime打开看是十六进制的代码,大致如下:

CAFE BABE 0000 0032 0016 0700...

类的生命周期

类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括: 加载loading、验证verfication、准备preparation、解析resolution、初始化initialization、使用using和卸载unloading7个阶段,其中验证、准备、解析3个部分称为连接,顺序图如下:

JAVA后端面试必会之JVM虚拟机类加载机制_第1张图片

类生命周期7个阶段的开始执行顺序

加载、验证、准备、初始化、卸载(图中标注成屎黄色)这个5个阶段的开始顺序是确定的,注意这里的开始顺序标注成了红色,开始顺序是确定的,但是进行或者完成的顺序不是顺序的,因为这些阶段的都是互相交叉地混合式进行的,通常在一个阶段的执行的过程中调用、激活另外一个阶段。

而解析阶段(图中标注成血红色)不一定,他在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或者晚期绑定)。

类加载的时机

Java虚拟机规范中并没有进行强制约束,这点是交给虚拟机的具体实现。

对于初始化阶段、虚拟机规范是严格规定了有且只有5中情况必须立即对类进行初始化(还记得之前说的开始顺序么,加载、验证、准备阶段必然于初始化之前开始)

5种情况参见《深入理解Java虚拟机一书》

类加载的过程

类加载的全过程,也就是加载、验证、准备、解析、初始化这5个阶段所执行的具体动作

加载是类加载过程的一个阶段,不要混淆,所以当被问到类加载过程的时候,不要单单指加载这个阶段。

加载 :JVM规范虚拟机规范规定了加载过程主要完成的三个事情

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

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

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

这三个事情具体是由虚拟机实现和具体的用户应用来决定的。

非数组类的加载阶段是我们开发人员可控性最强的一个阶段,因为加载阶段既可以使用系统提供的引导类加载器完成,也可以由用户自定义的类加载器去完成,开发人员可以定义自己的类加载器去控制字节流的获取方式。

注意上面标注了红色字体的非数组类,因为数组类本身是不通过类加载器创建,它是由jvm直接创建的,但是这不能说明数组类就和类加载器没有任何的关系,数组类中的元素类型最终还是要靠类加载器去创建。

综上加载阶段,实际就是将获取到的二进制字节流按照虚拟机所需的格式存储在方法区之中,这个格式由虚拟机自行定义,然后在“内存中”实例化一个java.lang.Class对象,这个“内存”虚拟机规范也并没有说明是堆还是方法区(hotspot虚拟机,就是将Class对象放到方法区里面)

验证:JVM规范主要规范了4个验证动作

1. 文件格式验证

2. 元数据验证

3. 字节码验证

4. 符号引用验证

是不是一定要开启验证这个阶段,因为字节码文件的来源不确定,我们可以写一个二进制文件流,伪造成.class文件。如果所编写的全部代码(包括自己编写以及使用第三方类库)都已经反复使用并且验证过,可以通过-Xverify:none参数来关闭虚拟机的大部分类验证措施,来缩短类加载时间。

准备阶段

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,类变量都将在方法区中进行分配。

类变量的定义为:

public static int value = 123;

那么,就会在方法区中初始化这个类变量 value 的值为0,为什么不是123呢,因为赋值操作在初始化阶段才会执行,赋值为123的putstatic指令是在程序编译后,存放在()方法中,在初始化阶段才会执行java方法

但是,如果类变量的定义为:

public static final int value = 123,即类变量字段的字段属性表中存在ConstantValue属性,那么准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123

你可能感兴趣的:(面试)