JVM学习总结——类加载子系统

前言

最近在 b 站上看了宋红康老师的 JVM 讲解视频,对 JVM 有了一个基本特此写一些博客来记录我的一些理解和总结,博客中所使用的部分图片是由宋红康老师画,我这里拿来使用一下,特此说明。

JVM具体架构

在上篇博客中我已经对JVM做了整体概述,接下我将对图片中的每一个小块,展开来进行说明。

JVM类加载子系统

在字节码文件中存储的各种有关类的信息都需要加载到虚拟机中之后才能使用,而且并不是随便一个以 .class 为后缀名的文件就可以称之为 java 的字节码文件,正确符合规范的字节码文件是需要满足一些特定的要求,经过一些校验和解析之后才能成功的加载到虚拟机中,实现这一系列功能的组件就叫做 JVM 类加载子系统(虚拟机类加载机制)

类加载过程
类的加载过程一共分7个阶段,分别是 加载验证准备解析初始化使用卸载。其中验证,准备,解析三个部分又可以统称为链接阶段,所以有时又会说类的加载过程是4个阶段,其中大部分阶段都会按照顺序做该阶段需要做的事情,只有解析阶段有时会例外,为什么会例外后面会说到。

类加载机制——加载环节

在加载环节虚拟机要完成以下3件事:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结果
  • 在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据访问接口

总结:虚拟机查找并加载二进制,在内存中生成 java.lang.Class 对象。
注意:虽然 Class 是一个对象但在 Hotspot中,它被存放在方法区中。

方法区的具体落地:
jdk1.7及之前 —— 永久代
jdk1.8及之后—— 元空间(使用本地内存)

类加载机制——链接阶段

链接阶段——验证

在验证阶段虚拟主要对字节码文件做一些校验,保证加载的字节码是符合 java 字节码规范的,其中包括:

  • 文件格式验证——是否以魔数开头,主次版本号是否在当前虚拟机的处理范围内等等
  • 元数据校验——对一些 java 的基本语法规则进行校验,如加载的类是否有父类,是否实现了父类接口中的所有方法等等
  • 字节码校验
  • 符号引用验证——在虚拟机将符号引用转换成直接引用时,会做该验证(此验证发生在解析阶段)
链接阶段——准备阶段

在准备阶段虚拟机主要为类的静态变量 (用 static修饰的变量)分配内存,并且将其初始化为默认值。这些变量使用的内存都将在方法区中进行分配

在此阶段需要注意的是:准备阶段一般是为类静态变量赋默认值,不同类型的变量有不同的默认值,例如:

int //默认值 0
long //默认值 0L
short //默认值 0
boolean //默认值 false
float //默认值 0.0f
double //默认值 0.0d
reference //引用类型(也就是类)默认值 null

有一些特殊情况存在:当一个变量是由 static final 修饰时

public static final int = 3;

这个变量在此阶段赋的默认值就是显示赋值的值,也就是我代码块中写的 3 而不是 int 类型的默认值 0

需要注意的是并不是所有的用 static final 修饰的字段都在准备阶段才执行,有一些特殊情况会使得用 static final 修饰的字段在初始化阶段执行,例如

static final Integer ints = Integer.valueOf(100);

像这种 static final 修饰的字段存在代码的执行的情况,是会在初始化阶段进行显示赋值,为什么是在初始化阶段进行赋值的呢,那是因为,只有在初始化阶段才会有实际 java 代码的执行,在类加载的其他几个阶段是没有代码执行的。

链接阶段——解析阶段

解析阶段虚拟机主要是将常量池中的符号引用替换成直接引用的过程。

符号引用:就是一些字面量的引用,和虚拟机内部数据结构和内存无关
直接引用:就是直接指向具体的内存地址,通过这个引用可以获取到对应地址中存储的数据与虚拟机内部数据结构和内存有关

常量池中的符号引用包含: 类符号引用,接口符号引用,字段符号引用以及方法的符号引用。
在解析阶段会将这些所有的符号引用一一对应的转换成直接引用

初始化阶段

初始化阶段虚拟机主要是执行类构造器方法,为静态变量赋予正确的初值

这里注意和前面的准备阶段进行区分,准备阶段是为静态变量赋默认的值,而这里是为静态变量赋显示的初值

static int a; //准备阶段,此时 a 的值为0
static int a = 3; //初始化阶段,此时 a 的值被显示的复制为 3

此过程是类装载的最后一个过程,在此阶段才会真正开始执行类中所定义的代码,也就是说在前面的几个过程中,没有那个过程执行过 java 代码

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

<clinit>()方法

clint 方法是由 java 编译器生成并有 JVM 调用的,开发者是无法自定义同名该方法,也无法在 java 程序中调用该方法

此方法是由类静态成员变量的赋值语句以及static语句块合并产生的,也就是说静态代码块也是在这个环节开始执行的;我们都知道在加载一个类之前,虚拟机总是会试图先去加载该类的父类,所以父类的 clinit 方法总是会在子类的clinit方法之前被调用,这里就可以解释为什么父类的 static 块优先于子类的 static 块执行

在初始化阶段的主要工作就是要执行 clinit 方法,但是在有些情况下初始化阶段不会构建 clinit 方法,不构建也就不会执行:

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

了解完此阶段后,要注意区分初始化阶段准备阶段
这里做一个小结

  • 使用 static final 修饰,且显示赋值中不涉及到方法或构造器调用的基本数据类型或 String 类型的显示赋值,是在链接阶段的准备环节进行
  • 除上述情况之外的静态变量都是在初始化阶段进行赋值的

最后

类的加载环节还剩使用和卸载,关于使用没什么好多写的,作为 java 语言的使用者,我们无时无刻不在使用着类,对于类的使用再熟悉不过了这里就不赘述了

拜拜了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值