JVM入门到入土-Java虚拟机寄存器指令集与栈指令集

JVM入门到入土-Java虚拟机寄存器指令集与栈指令集

  • HotSpot虚拟机中的任何操作都需要入栈和出栈的步骤。

  • 由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。

参考资料

  • Java虚拟机规范(JavaSE8)
  • 深入理解Java虚拟机

JVM的两大指令集特点

基于栈式架构的特点

设计和实现更简单,适用于资源受限的系统(HotSpot虚拟机就基于此):

  • 避开了寄存器的分配难题: 使用零地址指令方式分配。
  • 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。
  • 指令集更小编译器容易实现。
  • 不需要硬件支持,可移植性更好,更好实现跨平台

基于寄存器架构的特点

典型的应用是x86的二进制指令集: 比如传统的PC以及Android的Davlik虚拟机。

  • 指令集架构则完全依赖硬件,可移植性差
  • 性能优秀和执行更高效,花费更少的指令去完成一项操作。
  • 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。

获取栈指令集-指令javap

注意:如果你使用JDK17,可能会出现找不到控制台指令的问题(因为默认安装不会配置该指令),去JDK17的安装目录将bin添加到环境变量即可

先编译一个简单的1+2程序:

public class StackOneTest {
	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		int c = a+b;
	}
}

然后使用javap -c class文件全名,我们将在控制台得到如下内容:

D:\CodeProjects\Eclipse\StackTest\bin\testjava>javap -c StackOneTest.class
Compiled from "StackOneTest.java"
public class testjava.StackOneTest {
  public testjava.StackOneTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_3
       8: return
}

由于手里没有基于寄存器的指令集实验环境,可自行查阅查看方法

栈指令集字节码的语义

字节码指令集设计概述

在 Java 虚拟机的指令集中,大多数的指令都包含了其所操作的数据类型信息。例如,iload 指令用于从局部变量表中加载 int 类型的数据到操作数栈中,而 ad 指加载的则是 float 类型的数据。这两个指令的操作可能会是由同一段代码来实现的,但它们必须拥有各自独立的操作码。对于大部分与数据类型相关的字节码指令来说,它们的操作码助记符中都有特殊的字符来表明该指令为哪种数据类型服务:i 代表对 int 型的数据操作,l 代表 longs 代表 shortb 代表 bytec 代表 charf 代表 floatd 代表 doublea 代表 reference。也有一些指令的助记符没有明确用字母指明数据类型,例如 arraylength 指令,它没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。还有另外一些指令,例如,无条件跳转指令 goto 则是与数据类型无关的。

因为 Java 虚拟机的操作码长度只有一个字节,所以包含了数据类型的操作码给指令集的设计带来了很大的压力。如果每一种与数据类型相关的指令都支持 Java 虚拟机的所有运行时数据类型,那恐怕就会超出一个字节所能表示的数量范围了。因此,Java 虚拟机的指令集对于特定的操作只提供了有限的类型相关指令,换句话说,指令集将会故意设计成非完全独立的(not orthogonal,即并非每种数据类型和每一种操作都有对应的指令)。有一些单独的指令可以在必要的时候用来将一些不支持的类型转换为可支持的类型。

表 2-2 列举了 Java 虚拟机所支持的字节码指令集。用数据类型列所代表的特殊字符替换 opcode 列的指令模板中的 T,就可以得到一个具体的字节码指令。如果在表中指令模板与数据类型两列共同确定的单元格为空,则说明虚拟机不支持对这种数据类型执行这项操作。例如,load 指令有操作 int 类型的 iload,但是没有操作 byte 类型的同类指令。请注意,从表 2-2 中可以看出,大部分的指令都没有支持整数类型 bytecharshort,甚至没有任何指令支持 boolean 类型。编译器会在编译期或运行期将 byteshort 类型的数据带符号扩展(sign-extend)为相应的 int 类型数据,将 booleanchar 类型数据零位扩展(zero-extend)为相应的 int 类型数据。与之类似,在处理 booleanbyteshortchar 类型的数组时,也会转换为使用对应的 int 类型的字节码指令来处理。因此,操作数的实际类型为 booleanbytecharshort 的大多数操作,都可以用操作数的运算类型(computational type)为 int 的指令来完成。

from : Java虚拟机规范(JavaSE8)

附表:

在这里插入图片描述

在这里插入图片描述

实际类型与关系映射表:

在这里插入图片描述

加载与存储指令集

加载本地变量到操作数栈的指令:

指令描述
iload将 int 类型加载到操作数栈
iload <n>将指定索引的 int 类型加载到操作数栈
lload_<n>将指定索引的 long 类型加载到操作数栈
fload将 float 类型加载到操作数栈
fload <n>将指定索引的 float 类型加载到操作数栈
dload将 double 类型加载到操作数栈
dload <n>将指定索引的 double 类型加载到操作数栈
aload将引用类型加载到操作数栈
aload <n>将指定索引的引用类型加载到操作数栈

存储操作数栈到局部变量表的指令:

指令描述
istore将 int 类型存储到局部变量表
istore <n>将 int 类型存储到指定索引的局部变量
lstore <n>将 long 类型存储到指定索引的局部变量
fstore将 float 类型存储到局部变量表
fstore <n>将 float 类型存储到指定索引的局部变量
dstore将 double 类型存储到局部变量表
dstore <n>将 double 类型存储到指定索引的局部变量
astore将引用类型存储到局部变量表
astore_<n>将引用类型存储到指定索引的局部变量

加载常量到操作数栈的指令:

指令描述
bipush将带符号的 byte 常量(-128~127)加载到操作数栈
sipush将带符号的 short 常量(-32768~32767)加载到操作数栈
ldc将 int, float 或 String 类型的常量加载到操作数栈
ldc_w与 ldc 类似,但用于更大的常量池索引
ldc2_w将 long 或 double 类型的常量加载到操作数栈
aconst_null将 null 加载到操作数栈
iconst_m1将整数 -1 加载到操作数栈
iconst <i>将整数常量加载到操作数栈
lconst <1>将长整数常量1加载到操作数栈
fconst <f>将浮点数常量加载到操作数栈
dconst <d>将双精度浮点数常量加载到操作数栈

扩充局部变量表的访问索引或立即数的指令:

指令描述
wide扩展下一条指令使用的局部变量索引的宽度

算数指令集

算术指令:

操作类型指令
加法iaddladdfadddadd
减法isubIsubfsubdsub
乘法imulmulfmuldmul
除法idivldivfdivddiv
求余iremIremfremdrem

逻辑位运算指令:

操作类型指令
按位或iorlor
按位与iandland
按位异或ixorlxor

其他运算指令:

操作类型指令
求负值inegInegfnegdneg
移位ishlishriushrIshlIshrlushr
局部变量自增iinc
比较dcmpgdcmplfcmpgfcmplIcmp

栈指令集字节码分析

main方法中的算数字节码

D:\CodeProjects\Eclipse\StackTest\bin\testjava>javap -c StackOneTest.class
Compiled from "StackOneTest.java"
public class testjava.StackOneTest {
  public testjava.StackOneTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_3
       8: return
}

从上述字节码不难看出,JVM先执行了StackOneTest对象的构造,然后将这个对象变量加载到了操作数栈(默认构造器),我们重点来看main中的指令集:

0: iconst_1
1: istore_1
2: iconst_2
3: istore_2

先将两个常量12加载到操作数栈,并且进行了存储,接下来:

4: iload_1
5: iload_2
6: iadd
7: istore_3

加载了两个变量ab到操作数栈,然后使用算数指令进行相加,最后进行存储结果(这里的第三个变量未使用,变量加载可能被编译器优化掉了),我们稍加修改:

在这里插入图片描述

可以发现在使用了第三个变量后,JVM进行了正确的变量加载

含方法的字节码分析

我们增加一个方法来进一步分析:

public class StackOneTest {
	
	public static int add(int a, int b) {
		return a+b;
	}
	
	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		int c = add(a, b);
		System.out.println(c);
	}
}

在这里插入图片描述

从这里可知 invokestatic应该为调用方法的指令集,其运行也是和上一个大同小异,不过需要注意不同返回值类型的Treturn指令(T是借用泛型里的一个代号)

多次入栈字节码分析

修改代码:

public class StackOneTest {
	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		int c = 3;
		int d = a+b;
		int e = a+c;
	}
}

相同方法进行分析:

在这里插入图片描述

可以发现其字节码也是逐行进行操作的,在变量de位置的处理和第一处也是类似的,只不过a进行了重复入栈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/272162.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

华为设备VRP系统管理

为了满足企业业务对网络的需求&#xff0c;网络设备中的系统文件需要不断进行升级。另外&#xff0c;网络设备中的配置文件也需要时常进行备份&#xff0c;以防设备故障或其他灾害给业务带来损害。在升级和备份系统文件或配置文件时&#xff0c;经常会使用FTP和TFTP来传输文件。…

系统学习Python——装饰器:函数装饰器-[跟踪调用]

分类目录&#xff1a;《系统学习Python》总目录 如下的代码定义并使用了一个函数装饰器&#xff0c;统计对被装饰函数的调用次数&#xff0c;并且针对每一次调用打印跟踪信息&#xff1a; class tracer:def __init__(self, func):self.calls 0self.func funcdef __call__(se…

【Linux驱动】最基本的驱动框架 | LED驱动

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux驱动》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;最基本的驱动框架⚽驱动程序框架⚽编程 &#x1f3c0;LED驱动⚽配置GPIO⚽编程…

Python 高级(四):线程池 ThreadPoolExecutor

大家好&#xff0c;我是水滴~~ 当涉及到需要同时处理多个任务的情况时&#xff0c;使用线程池是一种高效的方法。Python提供了concurrent.futures模块&#xff0c;其中的ThreadPoolExecutor类使得使用线程池变得非常方便。本文将详细介绍Python线程池的概念、使用方法和示例代…

【网络安全 | SQL注入】一文讲清预编译防御SQL注入原理

在防止SQL注入的方法中&#xff0c;预编译是十分有效的&#xff0c;它在很大程度上解决了SQL注入问题。 SQL注入简析 数据库查询语句未对SQL注入做任何防护时&#xff0c;语句基本如下&#xff1a; $name$_POST[name]; $pass$_POST[pass]; $sql"SELECT * FROM user W…

展望2024的区块链世界,铭文将是绕不开的话题

近期&#xff0c;加密领域的热点焦点不断涌现&#xff0c;但毫无疑问&#xff0c;"铭文"这个词汇已经成为了近两个月内广受瞩目的关键词之一。像ORDI、SATS、RATS等铭文项目在比特币区块链上获得了惊人的增长&#xff0c;为持有者带来了巨大的财富效应。铭文热潮已经…

图片批量处理:图片批量缩放,高效调整尺寸的技巧

在数字媒体时代&#xff0c;图片处理已是日常生活和工作中不可或缺的一部分。有时候要批量处理图片&#xff0c;如缩放图片尺寸&#xff0c;以满足不同的应用需求。现在一起来看看办公提效式具如何高效的将图片批量处理方法&#xff0c;快速、准确地批量调整图片尺寸操作。 下…

SQL server 数据库练习题及答案(练习2)

使用你的名字创建一个数据库 创建表&#xff1a; 数据库中有三张表&#xff0c;分别为student,course,SC&#xff08;即学生表&#xff0c;课程表&#xff0c;选课表&#xff09; 问题&#xff1a; --1.分别查询学生表和学生修课表中的全部数据。--2.查询成绩在70到80分之间…

封装map和set

文章目录 封装mapset红黑树成员变量节点定义KeyOfTMapKeyOfTSetKeyOfT begin() && end()迭代器迭代器类operatoroperator- - insert 封装 map和set的底层都是通过红黑树来实现的&#xff0c;那么是怎么做到共用同一份代码&#xff0c;但让map存储的是键值对&#xff0…

多功能演示工具ProVideoPlayer2 mac特色介绍

ProVideoPlayer2 mac是用于大多数任何生产的首选多功能演示工具。ProVideoPlayer 2是一种动态视频播放和处理媒体服务器&#xff0c;可将视频映射&#xff08;包括播放和实时视频输入&#xff09;实时控制到一个或多个输出。包括实时效果&#xff0c;调度&#xff0c;网络同步和…

(AntV X6)vue2项目流程图实现

(AntV X6)vue2流程图实现 项目&#xff1a;gitLab/zhengzhouyuan 效果&#xff1a; 一、项目引入X6 npm install antv/x6 --save 二、引入相关插件 npm install --save antv/x6-plugin-clipboard antv/x6-plugin-history antv/x6-plugin-keyboard antv/x6-plugin-selection an…

海云安亮相2023北京国际金融安全论坛,助力金融企业数字化转型降本增效

近日&#xff0c;2023北京国际金融安全论坛暨金融科技标准认证生态大会在北京金融安全产业园成功举办。深圳海云安网络安全技术有限公司&#xff08;以下简称“海云安”&#xff09;受邀参展亮相此次大会。海云安作为国内领先的金融科技服务商&#xff0c;展示了开发安全系列产…

数据结构:图文详解 树与二叉树(树与二叉树的概念和性质,存储,遍历)

目录 一.树的概念 二.树中重要的概念 三.二叉树的概念 满二叉树 完全二叉树 四.二叉树的性质 五.二叉树的存储 六.二叉树的遍历 前序遍历 中序遍历 后序遍历 一.树的概念 树是一种非线性数据结构&#xff0c;它由节点和边组成。树的每个节点可以有零个或多个子节点…

输入两个时间,判断时间是否为非工作日,并且是日期否为同一天。是的话返回true,否返回false

工作遇到这么一个逻辑&#xff0c;前端回传两个时间&#xff08;必须是两个那一种&#xff09;。然后&#xff0c;我后端需要判断这两个时间是否为同一天&#xff0c;并且这个时间是否为非工作日&#xff0c;是的话返回true&#xff0c;反之返回false 代码&#xff1a; packa…

2023/12/26 work

1. 定义自己的命名空间myspace&#xff0c;并在myspace中定义一个字符串&#xff0c;实现求字符串大小的函数。 #include <iostream>using namespace std;namespace mynamespace {string name;unsigned int strlen(string name){return name.size();} }using namespace…

面试题之二HTTP和RPC的区别?

面试题之二 HTTP和RPC的区别&#xff1f; Ask范围&#xff1a;分布式和微服务 难度指数&#xff1a;4星 考察频率&#xff1a;70-80% 开发年限&#xff1a;3年左右 从三个方面来回答该问题&#xff1a; 一.功能特性 1)HTTP是属于应用层的协议&#xff1a;超文本传输协议…

手机蓝牙在物联网超市中的应用

超市一站式购物已进入城市的千家万户。然而人们在选购时却采用直接翻阅商品的方式&#xff0c;既不方便又不卫生甚至大大缩短食品类商品保质期&#xff0c;也给超市商品管理造成很大难度。物联网(The Internet of things)基于射频识别(RFID)、红外感应等技术&#xff0c;把物品…

【PostGIS】在Java中操作postgis——使用springboot+Maven+mybatis框架

前言&#xff1a; PostgreSQL15对应PostGIS安装教程及空间数据可视化 空间数据库-常用空间函数 完成PostGIS的安装与配置后&#xff0c;让我们来写一个Java操作postgis数据库的demo吧~ 使用工具&#xff1a; NavicatIDEA 一、PostGIS数据库准备 在Navicat中新建一个postgr…

云渲染UE4像素流送搭建(winows、ubuntu单实例与多实例像素流送)

windows/ubuntu20.4下UE4.27.2像素流送 像素流送技术可以将服务器端打包的虚幻引擎应用程序在客户端的浏览器上运行&#xff0c;用户可以通过浏览器操作虚幻引擎应用程序&#xff0c;客户端无需下载虚幻引擎&#xff0c;本文实现两台机器通过物理介质网线实现虚幻引擎应用程序…