4、JVM执行子程序

一、class文件结构

1、魔数与 Class 文件的版本

每个 Class 文件的头 4 个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的 Class 文件。使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动。文件格式的制定者可以自由地选择魔数值,只要这个魔数值还没有被广泛采用过同时又不会引起混淆即可
在这里插入图片描述

紧接着魔数的 4 个字节存储的是Class 文件的版本号:第 5 和第 6 个字节是次版本号(MinorVersion),第 7 和第 8 个字节是主版本号(Major Version)。 Java 的版本号是从 45 开始的,JDK 1.1 之后的每个 JDK 大版本发布主版本号向上加 1 高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能运行以后版本的 Class 文件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的 Class 文件。
在这里插入图片描述
我这里使用的是你jdk1.8。

2、常量池

常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项 u2 类型的数据,代表常量池容量计数值(constant_pool_count)。与 Java 中语言习 惯不一样的是,这个容量计数是从 1 而不是 0 开始的
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量比较接近于 Java 语言层面的常量概念,如文本字符串、声明为 final 的常量值等。 而符号引用则属于编译原理方面的概念,包括了下面三类常量:类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符

3、访问标志

用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被 声明为 final 等

4、类索引、父类索引与接口索引集合

这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。

5、字段表集合

描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量。 而字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。 字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本 Java 代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问 性,会自动添加指向外部类实例的字段。

6、方法表集合

描述了方法的定义

7、属性表集合

存储 Class 文件、字段表、方法表都自己的属性表集合,以用于描述某些场景专有的信息。如方法的代码就存储在 Code 属性表中。

8、字节码指令

Java 虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操 作数,Operands)而构成。

9、动态链接

既然是执行方法,那么我们需要知道当前栈帧执行的是哪个方法,栈帧中会持有一个引用(符号引用),该引用指向某个具体方法。

10、方法返回地址

方法退出方式有:正常退出与异常退出 理论上,执行完当前栈帧的方法,需要返回到当前方法被调用的位置,所以栈帧需要记录一些信息,用来恢复上层方法的执行状态。正常退出,上层方 法的 PC 计数器可以做为当前方法的返回地址,被保存在当前栈帧中。“异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部 分信息” 方法退出时会做的操作:恢复上次方法的局部变量表、操作数栈,把当前方法的返回值,压入调用者栈帧的操作数栈中,使用当前栈帧保存的返回地址 调整 PC 计数器的值,当前栈帧出栈,随后,执行 PC 计数器指向的指令。

11、附加信息

虚拟机规范允许实现虚拟机时增加一些额外信息,例如与调试相关的信息。 一般把动态连接、方法返回地址、其他额外信息归成一类,称为栈帧信息。

二、基于栈的字节码解释执行引擎

Java 编译器输出的指令流,基本上是一种基于栈的指令集架构,指令流中的指令大部分都是零地址指令,它们依赖操作数栈进行工作。与 基于寄存器的指令集,最典型的就是 x86 的二地址指令集,说得通俗一些,就是现在我们主流 PC 机中直接支持的指令集架构,这些指令依赖寄存器进行 工作。

1、基于栈的指令集

举个最简单的例子,分别使用这两种指令集计算“1+1”的结果,基于栈的指令集会是这样子的: iconst_1
iconst_1
iadd
istore_0
两条 iconst_1 指令连续把两个常量 1 压入栈后,iadd 指令把栈顶的两个值出栈、相加,然后把结果放回栈顶,最后 istore_0 把栈顶的值放到局部变量表的 第0个Slot中。

2、基于寄存器的指令集

mov eax,1
add eax,1
mov 指令把 EAX 寄存器的值设为 1,然后 add 指令再把这个值加 1,结果就保存在 EAX 寄存器里面。

基于栈的指令集主要的优点就是可移植,寄存器由硬件直接提供,程序直接依赖这些硬件寄存器则不可避免地要受到硬件的约束。栈架构指令集的主要 缺点是执行速度相对来说会稍慢一些。所有主流物理机的指令集都是寄存器架构也从侧面印证了这一点。

三、方法调用详解

1、静态解析

在编译器就可以确定,因此确定 静态分派的动作实际上不是由虚拟机来执行的。多见于:函数重载、模版等。

2、动态解析

需要在程序运行时才能决定到底调用那个函数模块,多见于多态,继承。
每个类在方法区会保存一张虚函数表,如果子类未重写父类的方法,则子类中的虚函数指针指向父类中的函数,如果重写了,则指向自己的函数。

在object中存在的动态链接会保存当前多想指向的虚函数表的地址,区寻找调用的函数。

四、jvm类加载机制

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

1、加载

虚拟机需要完成以下 3 件事情:

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

2、验证

是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。但从整体 上看,验证阶段大致上会完成下面 4 个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

3、准备

是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强 调一下,首先,这时候进行内存分配的仅包括类变量(被 static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值

4、解析

是虚拟机将常量池内的符号引用替换为直接引用的过程。

5、初始化

在初始化阶段,则根据程序员通过程 序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器()方法的过程。()方 法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序 所决定的。

五、类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这 句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一 个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

这里所指的“相等”,包括代表类的 Class 对象的 equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用 instanceof 关 键字做对象所属关系判定等情况

1、加解密案例(deencrpt 代码)

我们可以继承ClassLoader来实现自己的类加载器,实现特定的功能,例如加解密等服务。

加解密的项目中运用:可以使用把代码使用私钥加密,在解析阶段使用公钥解密。这样跟用户做项目时提供对应的公钥,自己提供私钥加密后的代码信 息。在类加载时使用公钥解密运行。这样可以可以确保源代码的保密性。

2、双亲委派模型

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

双亲委派模型好处:
Java类随着它的类加载器一起具备了带有优先级的层次关系,保证java程序稳定运行
在这里插入图片描述
启动类加载器(Bootstrap ClassLoader):这个类将器负责将存放在<JAVA_HOME>\lib 目录中的,或者被-Xbootclasspath 参数所指定的路径中的,并且是 虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接使用 null 代替即可。

扩展类加载器(Extension ClassLoader):这个加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载<JAVA_HOME>\lib\ext 目录中的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器(Application ClassLoader):这个类加载器由 sun.misc.Launcher $App-ClassLoader 实现。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这 个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值