JVM资料

JVM

2、类加载子系统

类加载就是 Java 虚拟机把描述类的数据从 Class 文件加载到内存的一个过程,并且在这个过程中对数据进行校验、转换解析和初始化,最终形成可以被 Java 虚拟机直接使用的 Java 类

类加载、连接和初始化都是在程序运行期间完成的,这个设计造成了一些性能上的开销,但同时增强了 Java 的跨平台性,同时具备了动态加载和动态连接这两个特性

image-20221005220739671

加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定;

按部就班地“开始”,而不是按部就班地“进行”或按部就班地“完成”,强调这点是因为这些阶段通常都是互相交叉地混合进行的,会在一个阶段执行的过程中调用、激活另一个阶段

非数组类加载具体流程:

  1. 通过一个类的全限定名称来获取定义这个类的 Class 文件字节流 (加载)

  2. 连接阶段对文件格式进行验证(魔数、主次版本号)(连接-验证-文件格式验证)

  3. 将这部分字节流所代表的静态存储结构转化为方法区的运行时数据结构;因为 Class 文件是一个静态的二进制文件,里面存放的是我们类中所有的一些常量、结构、字段、方法、属性,这些都是静态存储在 Class 文件中的,我们需要把它们转化为方法区所需要的一种运行时的数据结构**(加载)**

  4. 连接-验证-元数据验证

  5. 连接-验证-字节码验证(它所消耗的验证时间在 类加载过程中,JDK6 之后 JVM 和 Java编译器进行了联合优化,尽可能把更多校验辅助措施挪到 Java编译器里进行)

  6. 连接-准备-初始化0值

  7. 在堆内存中生成一个代表这个类的 Class 对象,这个堆中的对象就相当于是方法区的入口,这个类所有的字段和方法的访问入口都在这个对象里面,我们要通过这个对象去方法区访问这个类的数据**(加载)**

数组类本身不通过类加载器来创建,由 Java 虚拟机直接在内存中动态构造出来,但是数组中的元素,还是要靠类加载器来完成加载

数组类创建过程:

  • 如果数组的元素是引用类型,就递归采用非数组类的加载过程去加载这个元素,这个数组将会被标识在加载该类型的类加载的类名空间上
  • 如果数组的组件类型不是引用类型,Java 虚拟机把这个数组标记为与引导类加载器关联
  • 数组类的可访问性和它的元素类型可访问性一致;如果元素类型不是引用类型,它的数组类的可访问性默认是 public

2.1、加载

非数组类加载具体流程:

  1. 通过一个类的全限定名称来获取定义这个类的 Class 文件字节流 (加载)

  2. 连接阶段对文件格式进行验证(魔数、主次版本号)(连接-验证-文件格式验证)

  3. 将这部分字节流所代表的静态存储结构转化为方法区的运行时数据结构;因为 Class 文件是一个静态的二进制文件,里面存放的是我们类中所有的一些常量、结构、字段、方法、属性,这些都是静态存储在 Class 文件中的,我们需要把它们转化为方法区所需要的一种运行时的数据结构**(加载)**

  4. 连接-验证-元数据验证

  5. 连接-验证-字节码验证(它所消耗的验证时间在 类加载过程中,JDK6 之后 JVM 和 Java编译器进行了联合优化,尽可能把更多校验辅助措施挪到 Java编译器里进行)

  6. 连接-准备-初始化0值

  7. 在堆内存中生成一个代表这个类的 Class 对象,这个堆中的对象就相当于是方法区的入口,这个类所有的字段和方法的访问入口都在这个对象里面,我们要通过这个对象去方法区访问这个类的数据**(加载)**

数组类本身不通过类加载器来创建,由 Java 虚拟机直接在内存中动态构造出来,但是数组中的元素,还是要靠类加载器来完成加载

数组类创建过程:

  • 如果数组的元素是引用类型,就递归采用非数组类的加载过程去加载这个元素,这个数组将会被标识在加载该类型的类加载的类名空间上
  • 如果数组的组件类型不是引用类型,Java 虚拟机把这个数组标记为与引导类加载器关联
  • 数组类的可访问性和它的元素类型可访问性一致;如果元素类型不是引用类型,它的数组类的可访问性默认是 public

2.2、连接

加载阶段与连接阶段的部分动作是交叉进行的,加载阶段尚未完成,但是连接阶段可能已经开始了;

验证

验证是连接的第一步,这个阶段的目的就是确保 Class 文件字节流中包含的信息符合 《Java 虚拟机规范》,保证不会出现危害虚拟机的情况

验证阶段主要进行四个检验:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

文件格式验证:

主要验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理,主要是为了保证输入的字节流能正确地解析并存储在方法区内,只有通过了这个验证,这段字节流才会被允许存储进方法区;后面三个阶段的验证都是基于方法区内存储的字节流去验证的

元数据验证:

对类的字节码描述的信息进行语义分析和校验

  • 这个类是否有父类
  • 这个类的父类是否继承了不被允许被继承的类
  • 如果这个类不是抽象类,是否实现了其父类或接口之中要实现的方法

字节码验证:

通过数据流分析和控制流分析,确定语义是合法的、符合逻辑的;主要就是对类的方法体进行校验,保证被校验类的方法在运行时不会做出危害虚拟机的行为

符号引用验证:

符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作在连接的第三阶段解析的时候发生;

符号引用主要是验证跟据全限定名称是否能找到对应的类、是否存在合法的字段描述和方法描述、以及类的访问权限的校验;符号引用验证的目的是为了确保解析行为能正常执行

准备

准备阶段是为类中静态变量分配内存并设置类变量初始值的阶段;这个时候进行内存分配的仅包括类变量,不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中

静态变量所使用的内存都应在方法区中进行分配,但方法区本身就是一个逻辑上的区域

  • JDK7 之前,HotSpot 使用永久代来实现方法区,是符合这种逻辑概念的
  • JDK8 之后,类变量随着 Class 对象一起存放在堆中,方法区完成成为了一种逻辑上的概念

解析

解析阶段就是 Java 虚拟机将常量池内的符号引用替换为直接引用

2.5、初始化

初始化阶段就是根据程序员通过编码指定的计划去初始化类变量和其他资源

类的初始化是类加载的最后一个步骤,之前的几个步骤,基本都是由 Java 虚拟机来执行的,到了初始化阶段,Java 虚拟机才真正开始执行类中编写的 Java 程序代码

初始化阶段本质上就是执行类构造器 ()方法的过程

()方法是 Javac编译器自动收集类中的所有类变量的赋值动作的和静态语句块中的语句合并产生的;它不需要显式的调用父类构造器,Java 虚拟机会保证子类的 clinit()方法执行前,父类的 clinit()方法已经执行完毕,所以父类中定义的静态语句块要优于子类先执行;

()方法对于类或者接口来说并不是必须的,如果一个类中没有静态语句块,也没有对静态变量的赋值操作,编译器就可以不生成这个方法;

接口和类对于执行 ()方法的顺寻是不太一样的,执行接口的 clinit()方法不需要先执行父接口的 clinit()方法,因为只有当父接口中定义的变量被使用时,父接口才会被初始化

Java虚拟机必须保证一个类的 clinit()方法在多线程环境中被正确地加锁同步,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值