JVM的故事——虚拟机字节码执行引擎
虚拟机字节码执行引擎
文章目录
- 虚拟机字节码执行引擎
- 一、概述
- 二、运行时栈帧结构
- 三、方法调用
一、概述
执行引擎Java虚拟机的核心组成之一,它是由软件自行实现的,能够执行那些不被硬件直接支持的指令集格式。
对于不同的虚拟机实现,执行引擎可能会有解释执行和编译执行或者两种兼备,但是所有执行引擎的输入输出都是一样的,输入的是字节码二进制流,输出的是执行结果
二、运行时栈帧结构
Java虚拟机以方法为最基本的执行单元,栈帧则是虚拟机用于方法调用和方法执行背后的数据结构,它是虚拟机运行时数据区中的虚拟机栈的基本元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。一个栈帧需要分配多少内存具体虚拟机实现的栈内存布局形式。
以Java程序的角度来看,同一时刻,同一线程里面在调用堆栈的所有方法都处于执行状态。以执行引擎的角度来看,只有位于栈顶的栈帧才是生效的,其被称为当前栈帧,对应的方法被称为当前方法。栈帧结构如图8-1所示。
(一)局部变量表
局部变量表用于存放方法参数和方法内的局部变量,在Java程序被编译为class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需分配的局部变量表的最大容量。
局部变量表的容量以变量槽为最小单位,变量槽的大小并没有明确规定,只是说每个变量槽都需要可以存放boolean、 byte、char、short、int、float、reference或returnAddress类型的变量,这8种数据类型最大是32位。所以变量槽需要大于32位,但不一定是32位。
对于提到的8种数据类型,前6种和Java中的差不多,reference表示对一个对象实例的引用,returnAddress指向了一条字节码指令的地址,这种类型已经很少见了。像long和double这样64位的数据类型,Java虚拟机会以高位对齐的方法为其分配两个连续的变量槽空间。
Java虚拟机通过索引定位的方式使用局部变量表,若是32位的数据类型,索引N就对应着第N个变量槽,若是64位的数据类型,索引N就对应着第N和第N+1个变量槽。对于两个相邻的共同存放64位数据类型变量的变量槽,不允许单独访问某一个变量槽。
当一个方法被调用时,就会使用局部变量表来完成参数值到参数变量列表的传递过程。
为了节省栈帧所占用的内存空间,局部变量表中的变量槽是可以重用的。若当前字节码PC计数器已经超过了方法体内的某个变量计数范围,那么这个变量的变量槽就可以被其它变量重用。但有时候变量槽的复用会影响到垃圾收集。
代码8-1和8-2中的placeholder占用的空间都没有被回收,只有代码8-3的palceholder占用的空间被回收了。这是因为判断是否被回收的根本原因是:局部变量表中的变量槽是否还存在对placeholder的引用。8-1是因为执行System.gc的时候还在placeholder的作用域中;8-2是因为placeholder原本所占用的变量槽还没有被其它变量复用。8-3中,已经不在placeholder的作用域中了,并且int a=0复用了placeholder原本占用的变量槽。在这里如果手动给placeholder赋null值也是一样的,但是赋null值在经过即使编译优化后是会被当作无效操作消除掉的。
局部变量不像前面提到的类变量存在准备阶段,前文的类变量会在准备阶段被赋一个系统初始值,然后在初始化阶段被赋一个定义的初始值,所以即使代码中没有给类变量赋初始值也是可以的。但是局部变量如果定义了但没赋初始值,它就是完全不能使用的。
(二)操作数栈
操作数栈和局部变量表类似,也是在编译期间最大深度就写入了Code属性的max_stacks数据项之中。32位数据类型占一个栈帧,64位数据类型占两个栈帧。
在方法执行时,是先把运算涉及的操作数压入到栈中,然后调用运算指令,使对应的数出栈进行运算,再把结果入栈。栈中的元素类型与字节码指令指令需要严格匹配,如iadd指令,栈顶的两个元素必须是两个int类型的。
在概念模型中,两个不同栈帧作为不同虚拟机栈的元素,是完全独立的,但是大部分虚拟机实现中,栈帧是有重叠部分的。如图所示。
(三)动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在类加载阶段或者第一次使用符号引用被转化为直接引用,这是静态解析。在运行时符号引用转化为直接引用,这是动态连接。
(四)方法返回地址
当一个方法开始执行后,只有两种方式退出这个方法。第一种就是遇到一个方法返回的字节码指令,这种方式叫做”正常调用完成”。第二种就是遇到异常,并且还没有处理好遇到的异常,这种方式叫做”异常调用完成”。
无论采用哪种退出方式,方法退出后都必须回到最初方法被调用的位置。
方法退出过程等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者的操作数栈中,调整PC计数器的值以指向后一条指令。
(五)附加信息
在讨论概念时,一般会把动态连接、方法返回地址与其他附加信息全部归为一类,称为栈帧信息。
三、方法调用
方法调用并不涉及到具体的执行,只是确定方法的版本(调用哪个方法)。一切方法调用在class文件中只是符号引用,而不是实际运行时内存布局中的入口地址(直接引用)。这使得某些类在类加载期间甚至是运行期间才能确定目标方法的直接引用。
(一)解析
在类加载阶段,就有一部分符号引用转化为直接引用,这个前提是方法在程序运行前就有一个确定的版本。这类方法的调用被称为解析。
在Java中符合“编译器可知,运行期不可变”的,主要有静态方法和私有方法。
调用不同类型的方法,字节码指令集中有不同的指令:
invokestatic:调用静态方法
invokespecial:调用实例构造器方法、父类方法和私有方法
invokevirtual:调用虚方法
invokeinterface:调用接口方法
invokedynamic:先动态解析出调用点限定符所引用的方法,再执行该方法。
只要能被invokestatic和invokespecial调用的方法,都可在解析阶段确定唯一版本。有静态方法、私有方法、类的构造方法、父类方法四种,还有被final修饰的方法,不过被final修饰的方法被invokevirtual调用。这五种方法在类加载阶段就把符号引用替换为直接引用,它们被称为“非虚方法”,其它的方法被称为“虚方法”。
如图,静态方法sayHello只属于该类型,没有任何方式覆盖或者隐藏这个方法。
用javap指令查看字节码,发现的确是通过invokestatic方法调用的sayHello()方法
(二)分派
Java是一门面向对象的程序语言,它具备面向对象的3个基本特征:封装、继承、多态。本节的分派调用过程将会揭多态的一些基本体现。
1.静态分派
分派这个词本来就具有动态性,在书中英文是“Method Overload Resolution”,即应该属于8.2中的解析。不过很多中文资料都称这种行为为静态分派。
运行结果为: hello,guy!
hello,guy!
对于变量man和woman来说,Human是静态类型,而对应的Man和Woman是运行时类型(实际类型)。变量最终的静态类型在编译期可知,而实际类型在运行期才可以确定。
虚拟机在重载时是通过参数的静态类型作为依据的,在编译期间就根据参数的静态类型选择了调用的方法。静态分派最典型的就是重载,这发生在编译期间,所以实际上静态分派动作并不是由虚拟机执行的。
重载方法匹配并不一定就是完全对应的,也会自动转换,这个转换是有优先级的。比如字符’a’,它的重载匹配的方法是按照参数类型为char>int>long>float>double的顺序转型进行匹配。如果都没有,就会进行自动装箱,匹配到Character。把Character类型的参数再注释掉,会匹配Character实现的接口Serializable和 Comparable。再没有也有可能会匹配到父类Object。
2.动态分派
静态分派是和重载有着很大的关系,动态分派是和重写有着很大的关系。
运行结果是man say hello
woman say hello
woman say hello
代码中的两个变量静态类型都是Human,实际类型是Man和Woman,变量man的实际类型在之后还变成了Woman。
根据字节码,可以看出两个调用无论指令还是参数都一样,但是最终执行的目标方法不同,那么就是invokevirtual有着一些判断。
invokevirtual的解析过程大致为下面几步:
1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;不通过则返回java.lang.IllegalAccessError异常。
3)否则,按照继承关系从下往上依次对C的各个父类进行第二步的搜索和验证过程。
4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
所以说invokevirtual还会根据方法接收者的实际类型去选择方法版本。这个过程就是Java重写的本质,这种分派过程就是动态分派。
多态性的根源在于虚方法调用invokevirtual,所以字段是没有多态的。当子类有与父类同名字段,虽然内存中两个字段都存在,但实际上子类会覆盖父类的字段。
结果分析:首先子类隐式调用了父类的构造方法,父类构造方法调用的showMeTheMoney是虚方法,虚方法看的是实际类型,所以其实是Son::showMeTheMoney,这个时候Son的money字段还为0,输出了第一行。然后就到了Son的构造方法的调用,输出了第二行。主方法中访问money字段,看的是静态类型,所以是Father中的2。
3.单分派与多分派
方法的接收者与方法的参数统称为方法的宗量,根据宗量的多少可以把方法分为单分派和多分派。
编译阶段编译器选择的过程,也就是静态分派的过程。这时候选择目标的根据:1、静态类型是Father还是Son 2、参数类型是360还是QQ。这根据了两个宗量进行选择,所以静态分派过程是多分派。
运行阶段虚拟机的选择,也就是动态分派的过程。在执行“son.hardChoice(new QQ())”使,已经确定参数类型是QQ了,所以唯一影响选择的就是接收者的实际类型是Father还是Son。只根据了一个宗量进行选择,所以动态分派过程是单分派。
所以Java语言是一门静态多分派,动态单分派的语言。
4.虚拟机动态分派的实现
动态分派是执行非常频繁的动作,所以真正运行时不会如此频繁的去搜索元数据,而是会建立一个虚方法表。
虚方法表中存放着各个方法的实际入口地址,如果某个方法在子类中没有被重写,那么它在子类和父类的地址是一样的,都指向父类的实现入口。如图8-3,Son重写了Father的全部方法,所以没有指向Father的箭头,但是他们俩都没有重写Object的方法,所以都有指向Object的箭头。
相关文章:

JVM的故事——虚拟机字节码执行引擎
虚拟机字节码执行引擎 文章目录 虚拟机字节码执行引擎一、概述二、运行时栈帧结构三、方法调用 一、概述 执行引擎Java虚拟机的核心组成之一,它是由软件自行实现的,能够执行那些不被硬件直接支持的指令集格式。 对于不同的虚拟机实现,执行引…...

设计模式之适配器与装饰器
目录 适配器模式 简介 角色 使用 优缺点 使用场景 装饰器模式 简介 优缺点 模式结构 使用 使用场景 适配器模式 简介 允许将不兼容的对象包装成一个适配器类,使得其他类可以通过适配器类与原始对象进行交互,从而提高兼容性 角色 目标角色…...

服务器数据恢复- Ext4文件系统分区挂载报错的数据恢复案例
Ext4文件系统相关概念: 块组:Ext4文件系统的空间被划分为若干个块组,每个块组内的结构大致相同。 块组描述符表:每个块组都对应一个块组描述符,这些块组描述符统一放在文件系统的前部,称为块组描述符表。每…...

19-springcloud(上)
一 微服务架构进化论 单体应用阶段 (夫妻摊位) 在互联网发展的初期,用户数量少,一般网站的流量也很少,但硬件成本较高。因此,一般的企业会将所有的功能都集成在一起开发一个单体应用,然后将该单体应用部署到一台服务器…...

前端基础---HTML笔记汇总一
HTML定义 HTML超文本标记语言——HyperText Markup Language。 超文本是什么? 链接标记是什么? 标记也叫标签,带尖括号的文本 标签分类 单标签:只有开始标签,没有结束标签(<br>换行 <hr>水平线 <img> 图像标…...

智汇云舟亮相中国安防工程商集成商大会
智汇云舟亮相中国安防工程商集成商大会,以视频孪生驱动安防行业数字化转型 近日,由中国安全防范产品行业协会指导,永泰传媒主办的中国安防工程商(系统集成商)大会暨第69届中国安防新产品、新技术成果展示在石家庄圆满…...

使用 Sealos 在离线环境中光速安装 K8s 集群
作者:尹珉。Sealos 开源社区 Ambassador,云原生爱好者。 当容器化交付遇上离线环境 在当今快节奏的软件交付环境中,容器化交付已经成为许多企业选择的首选技术手段。在可以访问公网的环境下,容器化交付不仅能够提高软件开发和交付…...

算法-模拟
1、旋转数组 public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** 旋转数组* param n int整型 数组长度* param m int整型 右移距离* param a int整型一维数组 给定数组* return int整型一维数组*/…...

如何通过Instagram群发消息高效拓展客户?
之前小S有跟大家说过关于独立站+Instagram如何高效引流,发现大家都对Instagram的话题挺关注的。Instagram作为全球最受欢迎的社交媒体之一,对于许多商家和营销人员来说,Instagram是一个不可忽视的营销平台,他们可以通过…...

基于springboot实现多线程抢锁的demo
1、本代码基于定时调度和异步执行同时处理,如果只加异步处理,会导致当前任务未执行完,下个任务到点也不会触发执行 Scheduled(fixedRate 50_000)Asyncpublic void testThread() throws Exception{ZkLock lock new ZkLock(zkJob.getZK(), &q…...

Java I/O模型发展以及Netty网络模型的设计思想
Java I/O模型发展以及Netty网络模型的设计思想 I/O模型Java BIOJava NIOJava AIO NIO Reactor网络模型单Reactor单线程模型单Reactor多线程模型主从Reactor多线程模型 Netty通信框架 前言: BIO、NIO的代码实践参考:Java分别用BIO、NIO实现简单的客户端服…...

智能电网时代:数字孪生的崭露头角
随着科技的不断进步,数字孪生已经开始在电力行业崭露头角,为这个关键的行业带来了前所未有的机遇和潜力。本文就带大家了解一下数字孪生在哪些方面为电力行业做出改变,以及未来的创新应用。 首先,数字孪生可以提高电力系统运营效率…...

每日一题 501二叉搜素树中的众数(中序遍历)
题目 给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。 如果树中有不止一个众数,可以按 任意顺序 返回。 假定 BST 满足如下定义&a…...

测试理论与方法----测试流程第三个环节:设计测试用例
测试流程第三个环节:设计测试用例:怎么测<——>测试需求的提取:测什么 ### 5、测试用例 描述:测试用例(TestCase):是一份关于【具体测试步骤】的文档,是为了达到最佳的测试效果或高效揭露软件中潜藏的…...

C++多态案例2----制作饮品
#include<iostream> using namespace std;//制作饮品的大致流程都为: //煮水-----冲泡-----倒入杯中----加入辅料//本案例利用多态技术,提供抽象类制作饮品基类,提供子类制作茶叶和咖啡class AbstractDrinking {public://煮水//冲水//倒…...

机械零件保养3d模拟演示打消客户购买顾虑
复杂机械的工作运转是复杂的,想要对机械有深度的理解和迭代,必须了解它的运转原理及参数,复杂机械运行原因教学存在着不可视、系统庞杂及知识点多等弊病,3D虚拟展示是基于web3d网页运行的三维页面,可以将复杂机械运行过…...

SpringBoot的自动装配源码分析
文章目录 一:什么是自动装配二、springboot的启动流程1.调用SpringApplication()的构造方法2.执行核心run方法()3.执行核心prepareContext()4.执行核心refreshContext()5…...

Linux常用命令——csplit命令
在线Linux命令查询工具 csplit 将一个大文件分割成小的碎片文件 补充说明 csplit命令用于将一个大文件分割成小的碎片,并且将分割后的每个碎片保存成一个文件。碎片文件的命名类似“xx00”,“xx01”。csplit命令是split的一个变体,split只…...

React 组件的3大属性: state
state 一、理解二、用途三、使用3.1、类初始化3.2、函数初始化 四、状态读更4.1、组件内部状态管理和数据更新4.2、state 和 props 一起使用 一、理解 组件被称为"状态机", 页面的显示是根据组件的state 属性的数据来显示。 state 是一个用于存储和管理组件内部数据的…...

vscode 上传项目到gitlab
第一步初始化项目 如果没有创建过分支(创建分支这里不记录),默认是master分支: ①将所需要的上传的文件添加到暂存区,如图: ②填写一下注释信息,将暂存区的文件上传到本地分支(没有创…...

[羊城杯 2020] easyphp
打开题目,源代码 <?php$files scandir(./); foreach($files as $file) {if(is_file($file)){if ($file ! "index.php") {unlink($file);}}}if(!isset($_GET[content]) || !isset($_GET[filename])) {highlight_file(__FILE__);die();}$content $_GE…...

QT 常用类与组件
0 思维导图 1 信息调试类(QDebug) #include "widget.h" #include<iostream> //printf #include<QDebug> //qDebuf using namespace std; //coutWidget::Widget(QWidget *parent): QWidget(parent) {//输出函数//使用…...

C#控制台连接Mysql数据库,有配置数据库连接字符串的配置文件
C#控制台连接Mysql数据库,有配置数据库连接字符串的配置文件 实现功能 读取..txt 中的配置文件,来初始化连接字符串让连接字符串的配置文件不存在会主动创建默认的连接字符串 注意点: 需要引用Newtonsoft使用mysql 代码如下 using Syst…...

PowerBuilder连接SQLITE3
PowerBuilder,一个古老的IDE,打算陆续发些相关的,也许还有人需要,内容可能涉及其他作者,但基本都是基于本人实践整理,如涉及归属,请联系. SQLite,轻型数据库,相对与PowerBuilder来说是个新事务,故发数来,以供参考. PB中使用OLE Microsoft OLE DB方式进行连接,如下 // Profile…...

Git 基本原理和常用操作
Git Git 是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。由 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开源的版本控制软件。 Git 常用操作 git 提交流程:工作区 -> git add 到暂存区 -> gi…...

单元测试和集成测试的区别
单元测试和集成测试是软件开发中常用的两种测试方法,它们的主要区别如下: 范围不同:单元测试关注于对软件中的最小功能单元进行测试,通常是对独立的函数、方法或类进行测试。而集成测试则更加综合,涉及多个模块、组件或…...

node基础概念
前言:可以让别人访问我们的网页,可以开发服务端应用、工具类应用、桌面端应用(electron) 1. 计算机基础 概念:CPU 内存 硬盘 主板 显卡 2. 进程和线程 概念:进程是一个程序的执行,线程组合形…...

ArcGIS Maps SDK for JS(二):MapView简介----创建2D地图
文章目录 1 AMD 引用 ArcGIS Maps SDK for JavaScript2 加载相应模块3 创建地图4 创建 2D 视图 view5 确定页面内容6 CSS 样式7 完整代码 本教程使用 AMD 模块,指导您如何在二维地图视图中创建一个简单的地图。 1 AMD 引用 ArcGIS Maps SDK for JavaScript 在 <…...

知识图谱推理研究综述9.3
综述分类 根据样本量大小的不同,将知识图谱推理方法分为多样本推理、少样本推理和零与单样本推理 KG定义:(Y) 知识图谱是以图的形式表示真实世界的实体与关系之间关系的知识库。 具体来说知识图谱是通过将应用数学、图形学、信…...

详细介绍c++中的类
C 中的类是面向对象编程的基本概念,它指的是一种能够封装数据和方法的用户定义数据类型。类是程序中一个重要的概念,它允许程序员通过定义类来实现代码复用、模块化和继承等特性。 C 中的类由以下部分组成: Data members:成员变量…...