ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JVM虚拟机方法调用和选择

2021-11-08 21:30:00  阅读:121  来源: 互联网

标签:调用 虚拟机 局部变量 分派 JVM 执行 方法


虚拟机是JVM最核心的组成部分之一。

物理机和虚拟机的区别
二者都有代码执行的能力
物理机的执行引擎是建立在处理器、硬件、指令集和操作系统层面上的。
虚拟机的执行引擎是自己实现的,可以自行指定指令集与执行引擎结构体(包括不被硬件支持的指令集格式)

PC物理机支持的指令架构基于寄存器的指令集。这个的优点是执行速度相对比较快。
Javac编译器输出的字节码指令流,基本上是一种基于栈的指令集架构。基于栈的指令集主要优点是可移植,因为寄存器由硬件直接提供,程序直接依赖这些硬件寄存 器则不可避免地要受到硬件的约束。缺点是速度比较慢,完成相同的功能栈指令架构所需要的指令数比寄存器架构多,而且栈是在内存中的,频繁的栈访问也就意味着频繁的内存访问。内存的速度肯定要比寄存器的速度慢。因此由于指令数量和内存访问的原因,导致了栈架构指令集的 执行速度会相对慢上一点。

JVM执行引擎概念模型, 就是一个理论模型:执行引擎输入的是字节码,处理过程是字节码解析的等效过程,输出的是执行结果。所有JVM执行引擎都是这个模式,但是不同的JVM可以选择不同的执行过程,也就处理过程可以不同,可选择解释执行、编译执行、解释+编译二者兼备。

运行时栈帧结构

Java虚拟机以方法作为最基本的执行单元,“栈帧”(Stack Frame)则是用于支持虚拟机进行方法 调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈的栈元素。

在这里插入图片描述
一个虚拟机栈中可以包含多个栈帧,只有位于最顶层的栈帧才会被执行,执行完毕后此栈帧退出虚拟机栈,执行下一个栈帧。每个栈帧存储了局部变量表、操作栈、动态连接、返回地址等信息。

局部变量表:一组变量值的存储空间,用于存放方法参数和方法内部定义 的局部变量。Class文件中Code属性的max_locals数据项中确定了该方 法所需分配的局部变量表的最大容量。局部变量表的容量以变量槽(Variable Slot)为最小单位。槽的具体大小有32位的、有64位的。一个变量槽可以存放一个 32位以内的数据类型,具体数据类型有有boolean、byte、char、short、int、 float、reference和returnAddress这8种类型。returnAddress是以前用来处理操作异常的跳转,目前已经被异常表所取代。

64位的数据(double、long)等在32位虚拟机下,需要读写两次,非原子操作。但是在64位虚拟机下,64位数据可以实现原子操作。以下均假设槽大小是32位:Java虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的变 量槽数量。如果访问的是32位数据类型的变量,索引N就代表了使用第N个变量槽,如果访问的是64位 数据类型的变量,则说明会同时使用第N和N+1两个变量槽。不允许单独访问64位变量中的某一个槽。

对于类变量,在类加载过程中有两次赋值操作,一次是在准备阶段变量赋予默认初值,一次是初始化阶段变量赋予程序定义的初值。但是局部变量声明之后,Java虚拟机不会自动给他初始化为默认值,因此局部变量要经过显示的初始化。局部变量如果参与运算或者直接输出等其他情况,必须要进行初始化,而假如局部变量只是接受表达是的结果可以不初始化。类变量即使不显示初始化,系统也会给一个默认初值,程序不会报错,但是局部变量不显示初始化,程序编译会报错。

操作数栈:操作数栈的最大深度在编译的时候被写入到Code属性的max_stacks数据项 之中。操作数栈的每一个元素都可以是包括long和double在内的任意Java数据类型。32位数据类型所占 的栈容量为1,64位数据类型所占的栈容量为2。两个栈帧作为不同方法的虚拟机栈的元素是完全相互独立的,但虚拟机会进行一些优化,令两个栈帧出现一部分重叠。不仅节约空间,而且方法调用时无需进行参数复制传递。

在这里插入图片描述
动态连接:Class文件的常量池中存 有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。符号引用部分会在类加载的解析阶段或者第一次使用的时候转化为直接引用,称为静态解析。另外一部分将在每次运行期间都转化为直接引用,称为动态连接。

方法返回地址:方法正常执行完毕会返回给上层的方法调用者,称为”正常调用完成“,方法执行过程中遇到了异常,并且这个异常没有在方法体内得到妥善处理,也会导致方法退出,称为”异常调用 完成“。无论何种完成,总总是要返还给上层调用者,需要一个返回地址。正常调用完成将调用者的程序计数器的值作为返回地址,这个值保存在栈帧中,而异常调用完成需要靠异常表来确定返回地址,栈帧不会保存这些。

附加信息:一些规范里没有描述的信息到栈帧之中,例如与调试、 性能收集相关的信息,这部分信息完全取决于具体的虚拟机实现。一 般会把动态连接、方法返回地址与其他附加信息全部归为一类,称为栈帧信息。

方法调用

方法调用并不等同于方法中的代码被执行,只是是确定被调用方法的版本。一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(直接引用)。方法调用的目标方法在Class文件中是常量池的一个符号引用,在类加载中会有一部分符号引用转为直接引用,称为解析,另一部分在每次运行期间都转化为直接引用,叫做动态连接。

能够解析的前提一定是这个方法在运行前的版本就是却确定的,并且这个确定的版本在方法运行期间不可变。调用目标在程序代码写好、编译器进行编译那一刻就已经确定下来,比如私有方法、静态方法、实例构造器、父类方法以及被final修饰的方法。

分派
上面的几种情况是在累加载的时候就完成符号引用到直接引用的转变

静态分派:所有依赖静态类型来决定方法执行版本的分派动作,都称为静态分派。静态分派的最典型应用表 现就是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的,因此也有人将他归入为解析。重载的时候,根据传入参数的静态类型(也就是方法接受参数传入的类型)来确定,而不是实例类型(这个参数真正是何类型)。

动态分派:动态分派的典型应用就是重写。不在类加载时期解析的方法统称为虚方法,虚方法执行时会调用invokevirtual指令,其解析过程如下:
1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果 通过则返回这个方法的直接引用,查找过程结束;不通过则返回java.lang.IllegalAccessError异常。
3)否则,按照继承关系从下往上依次对C的各个父类进行第二步的搜索和验证过程。
4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
这个。

重写的根源就是虚方法调用的invokevirtual,所以不使用这条语句的就无法重写。虚方法会调用,而字段不会调用。子类声明了和父类相同的字段时会直接覆盖。

静态多分派、动态单分派:静态分派的时候根据静态变量来选择方法,此时有多个选择。而动态分派时根据方法接受者的实际类型决定,这个方法时确定的,只有一个选择。

动态分派实现

Java虚拟机实现基于执行性能的考虑,真正运行时一般不 会如此频繁地去反复搜索类型元数据。所以为类型在方法区中建立一个虚方法表。虚方法表中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那子类的虚方 法表中的地址入口和父类相同方法的地址入口是一致的,都指向父类的实现入口。如果子类中重写了 这个方法,子类虚方法表中的地址也会被替换为指向子类实现版本的入口地址。

在这里插入图片描述

标签:调用,虚拟机,局部变量,分派,JVM,执行,方法
来源: https://blog.csdn.net/weixin_44475591/article/details/121197952

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有