当前位置: 首页 > news >正文

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。 超文本是什么&#xff1f; 链接标记是什么&#xff1f; 标记也叫标签&#xff0c;带尖括号的文本 标签分类 单标签:只有开始标签&#xff0c;没有结束标签(<br>换行 <hr>水平线 <img> 图像标…...

智汇云舟亮相中国安防工程商集成商大会

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

使用 Sealos 在离线环境中光速安装 K8s 集群

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

算法-模拟

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

如何通过Instagram群发消息高效拓展客户?

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

基于springboot实现多线程抢锁的demo

1、本代码基于定时调度和异步执行同时处理&#xff0c;如果只加异步处理&#xff0c;会导致当前任务未执行完&#xff0c;下个任务到点也不会触发执行 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通信框架 前言&#xff1a; BIO、NIO的代码实践参考&#xff1a;Java分别用BIO、NIO实现简单的客户端服…...

智能电网时代:数字孪生的崭露头角

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

每日一题 501二叉搜素树中的众数(中序遍历)

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

测试理论与方法----测试流程第三个环节:设计测试用例

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

C++多态案例2----制作饮品

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

机械零件保养3d模拟演示打消客户购买顾虑

复杂机械的工作运转是复杂的&#xff0c;想要对机械有深度的理解和迭代&#xff0c;必须了解它的运转原理及参数&#xff0c;复杂机械运行原因教学存在着不可视、系统庞杂及知识点多等弊病&#xff0c;3D虚拟展示是基于web3d网页运行的三维页面&#xff0c;可以将复杂机械运行过…...

SpringBoot的自动装配源码分析

文章目录 一&#xff1a;什么是自动装配二、springboot的启动流程1.调用SpringApplication&#xff08;&#xff09;的构造方法2.执行核心run方法&#xff08;&#xff09;3.执行核心prepareContext&#xff08;&#xff09;4.执行核心refreshContext&#xff08;&#xff09;5…...

Linux常用命令——csplit命令

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

React 组件的3大属性: state

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

vscode 上传项目到gitlab

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

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

前端高频面试题2:浏览器/计算机网络

本专栏相关链接 前端高频面试题1&#xff1a;HTML/CSS 前端高频面试题2&#xff1a;浏览器/计算机网络 前端高频面试题3&#xff1a;JavaScript 1.什么是强缓存、协商缓存&#xff1f; 强缓存&#xff1a; 当浏览器请求资源时&#xff0c;首先检查本地缓存是否命中。如果命…...

【大模型】RankRAG:基于大模型的上下文排序与检索增强生成的统一框架

文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构C.1 指令微调阶段C.2 排名与生成的总和指令微调阶段C.3 RankRAG推理&#xff1a;检索-重排-生成 D 实验设计E 个人总结 A 论文出处 论文题目&#xff1a;RankRAG&#xff1a;Unifying Context Ranking…...

前端工具库lodash与lodash-es区别详解

lodash 和 lodash-es 是同一工具库的两个不同版本&#xff0c;核心功能完全一致&#xff0c;主要区别在于模块化格式和优化方式&#xff0c;适合不同的开发环境。以下是详细对比&#xff1a; 1. 模块化格式 lodash 使用 CommonJS 模块格式&#xff08;require/module.exports&a…...

算法250609 高精度

加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...

OpenGL-什么是软OpenGL/软渲染/软光栅?

‌软OpenGL&#xff08;Software OpenGL&#xff09;‌或者软渲染指完全通过CPU模拟实现的OpenGL渲染方式&#xff08;包括几何处理、光栅化、着色等&#xff09;&#xff0c;不依赖GPU硬件加速。这种模式通常性能较低&#xff0c;但兼容性极强&#xff0c;常用于不支持硬件加速…...