JVM详解之类加载

一、类的加载过程(生命周期)

1. 说说类加载分为几步

1-1都谁需要加载?

在Java中数据类型分为基本数据类型和引用数据类型。

基本数据类型由虚拟机预先定义,引用数据类型则需要进行类加载

1-2.类加载的步骤

装载

链接:

​ 验证

​ 准备

​ 解析

初始化

类的使用

类的销毁

2. 类加载流程图

3. Loading(装载)阶段

3-1 装载阶段做了什么

所谓装载,就是将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型----类模板对象
装载阶段,就是查找并加载类的二进制数据,生成Class的实例

3-2 类模板对象

类模板对象,就好比Java类的"设计图纸",JVM将其都存在"方法区"中,有了类模板对象,才能在程序运行时通过反射机制来了解和操作这个了类
JDK1.8 之前,存在方法区 但 JDK1.8 之后,存在元空间

3-3 二进制流的获取方式

3-4 Class实例的存储位置

将.clas文件加载至元空间后,会在堆中创建一个Java.long.Class对象,用来封装类位于方法区内的数据结构
该Class对象是在加载类的过程创建的,每个类都应有一个Class类型的对象。

3-5 数组类的加载

数组类本身并不是由类加载器负责创建的,而是由JVM在运行时根据需要而直接创建的,但数组的元素类型仍需要依靠类加载器去创建。
创建数组类的过程:

如果数组元素类型是引用类型,那就遵循定义的加载过程递归加载和创建数组的元素类型

JVM使用指定的元素类型和数组维度来创建新的数组类

如果数组的元素类型是引用数据类型,数组类的可访问性就由元素类型的可访问性决定。

4. Linking(链接)阶段

4-1 验证

验证是链接操作的第一步,它的目的是保证加载的字节码是合法,合理并符合规范的

4-2 准备

准备阶段就是为类的静态变量分配内存,并将其初始化为默认值。
注意:

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

5. 解析

简而言之,将类,接口,字段和方法的符号引用转为直接引用

5 Initialization(初始化)阶段

5-1 子类加载前先加载父类

在加载类之前,虚拟机会先加载该类的父类。
因此父类的<clinit>总是在子类的<clinit>之前被调用。因此父类的static块优先级高于子类

5-2 那些类不会生成<clinit>方法

5.2.1 类中没有声明任何类变量,也没有静态代码块
5.2.2 类中声明类变量,但没使用类变量的初始化语句以及静态代码块来执行初始化操作
5.2.3 类中包含static final修饰的基本数据类型的字段

/**

* 哪些场景下,java编译器就不会生成<clinit>()方法

*/

public class InitializationTest1 {

//场景1:对于非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法

public int num = 1;

//场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法

public static int num1;

//场景3:比如对于声明为static final的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法

public static final int num2 = 1;

}

5-3 static与final的搭配问题

5-4 <clinit>()的调用会死锁吗

虚拟机为了保证<clinit>()方法只执行一次,会为这个方法上一把锁。这意味着,即使多个线程同时初始化同一个类,也只有一个线程能进去执行,其他线程必须在外面排队等待。这种机制保证了线程安全,但如果初始化方法本身耗时过长,就会导致所有等待的线程都被阻塞,引发一种难以排查的死锁问题。不过一旦类初始化完成后,后续需要使用这个类的线程就可以直接享受成果,无需再等待。

5-5 类的初始化情况:主动使用 vs 被动使用

主动使用:new一个新对象、调用静态方法、访问静态变量、强制要求加载类、子类调用父类成员、接口定义默认方法、类包含main方法
被动使用:被动使用即不进行初始化操作
全部评论

相关推荐

评论
2
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务