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

熟悉JVM字节码指令

简介

  • Java虚拟机的指令由一个字节长度(256个操作码)的、代表着特定操作含义的数字(操作码,Opcode)和跟随其后的零至多个代表其操作需要的参数(操作数,Operand)构成。
  • 由于Java虚拟机采用面向操作数栈的架构,所以大部分指令都只有一个操作码,操作数都是放到操作数栈中。
  • 如果不考虑异常,则简单的指令执行模型的伪代码如下:
do {  
    自动计算PC寄存器的值加1; 
    根据PC寄存器指示的位置,从字节码流中取出操作码;
     if (字节码存在操作数) {
        从字节码流中取出操作数; 
    }
    执行操作码所定义的操作;
} while (字节码流长度 > 0);

字节码与数据类型

  • 在Java虚拟机指令集中,大多少指令都包含其操作的数据类型信息。
  • 对于大部分与数据类型相关的指令,它们的操作码助记符中都有特殊字符来专门表明该指令服务的数据类型:i代表对int类型的数据操作
    • i代表int
    • l代表long
    • s代表short
    • b代表byte
    • c代表char
    • f代表 float
    • d代表double
    • a代表reference
  • 如下图是Java虚拟机指令集所支持的数据类型:(其中操作码如iadd,是操作码助记符)

在这里插入图片描述

加载和存储指令

加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
这类指令包括:

  • 将一个局部变量加载到操作数栈:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
  • 将一个数值从操作数栈存储到局部变量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
  • 将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
  • 扩充局部变量表访问索引的指令:wide

运算指令

运算指令用于对2个操作数栈上的值进行特定操作,并将结果重新存入操作数栈顶。
所有运算指令包括:

  • 加法指令:iadd、ladd、fadd、dadd
  • 减法指令:isub、lsub、fsub、dsub
  • 乘法指令:imul、lmul、fmul、dmul
  • 除法指令:idiv、ldiv、fdiv、ddiv
  • 求余指令:irem、lrem、frem、drem
  • 取反指令:ineg、lneg、fneg、dneg
  • 位移指令:ishl、ishr、iushr、lshl、lshr、lushr
  • 按位或指令:ior、lor
  • 按位与指令:iand、land
  • 按位异或指令:ixor、lxor
  • 局部变量自增指令:iinc
  • 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp

类型转换指令

类型转换指令可以将2种不同数组类型相互转换。

  • Java虚拟机直接支持数值宽泛类型转换,即小范围向大范围转换。(安全的,不会丢失精度:如int到long、float或者double;long到float或者double;float到double)
  • 窄化类型转换(可能会发生上限溢出、下限溢出和精度丢失等情况),需要用户显示类型转换,指令包括:
    • i2b - int转换为byte
    • i2c - int转换为char
    • i2s - int转换为short
    • l2i - long转换为int
    • f2i - float转换为int
    • f2l - float转换为long
    • d2i - double转换为int
    • d2l - double转换为long
    • d2f - double转换为float

对象创建与访问指令

Java虚拟机对对象和数组采用了不同的指令,对象创建后,就可以使用对象访问指令来获取对象或者数组中的字段或者数组元素。这些指令包括:

  • 创建类实例的指令:new
  • 创建类数组的指令:newarray、anewarray、mutilanewarray
  • 访问类字段指令:getstatic、putstatic
  • 访问实例字段指令:getfield、putfield
  • 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
  • 将一个操作数栈中的值储存到数组元素中的指令:bastore、castore、sastore、iastore、lastore、fastore、dastore、aastore
  • 取数组长度的指令:arraylength
  • 检查类实例类型的指令:instanceof、checkcast

操作数栈管理指令

如同操作一个普通数据结构栈的一样,Java虚拟机提供了一些操作操作数栈的指令,包括:

  • 将操作数栈顶的一个或者2个元素出栈:pop、pop2
  • 复制栈顶一个或2个数值,并将复制值或双份复制值重新压入到栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
  • 将栈最顶端的2个数值交换:swap

控制转移指令

控制转移指令可以让Java虚拟机有条件或无条件的从指定位置的下一条指令继续执行程序。(概念模型上理解其实就是修改PC寄存器的值)
指令包括:

  • 条件分支指令:ifeq、iflt、ifle、ifgt、ifge、ifnull、ifnotnull、if_icmpeq、ificmpne、ificmplt、ificmpgt、ificmple、ificmpge、ifacmpeq和ifacmpne
  • 复合条件分支指令:tableswitch、lookupswitch
  • 无条件分支:goto、goto_w、jsr、jsr_w、ret

方法调用和返回指令

方法调用的5条指令:

  • invokevirtual指令:用于调用对象的实力方法。
  • invokeinterface指令:用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出合适的方法进行调用。
  • invokespecial指令:用于调用一些需要特殊处理实例方法,包括实例初始化方法、私有方法和父类方法。
  • invokestatic指令:用于调用类静态方法。
  • invokedynamic指令:用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。

前面4条调用指令的分派逻辑都固化在虚拟机内部,用户无法改变,而invokedynamic指令的分派逻辑是有用户所设定的引导方法决定的。

方法调用指令与数据类型无关,而返回指令则与数据类型相关,包括:

  • ireturn指令:返回值是boolean、byte、char、short和int类型时使用。
  • lreturn指令:返回值是long
  • freturn指令:返回值是float
  • dreturn指令:返回值是double
  • areturn指令:返回值是对象
  • return指令:返回值为void

异常处理指令

  • athrow指令:显示抛出异常指令。
  • 自动抛出异常,如1/0时会抛出 ArithmeticException异常。
  • 在Java虚拟机中,处理异常(cat ch语句)不是由字节码指令来实现的(很久之前曾经使用jsr和 ret指令来实现,现在已经不用了),而是采用异常表来完成。

同步指令

Java虚拟机可以支持方法级同步和一段指令序列的同步,这两种同步结构的实现都是使用管程(monitor,锁)来实现的。

  • 方法级的同步是隐式的,无需通过字节码指令实现。虚拟机可以从类文件的方法表结构种的ACC_SYNCHRONIZED访问标志得知一个方法是不是同步方法。当方法被调用时,如果被设置了同步方法则需要先获取管程(锁),才能执行方法,执行完成后释放管程(锁)。
  • 同步一段指令序列,则是由synchronized对应的monitorenter和monitorexit这2条字节码指令实现的。

在这里插入图片描述

编译器会自动产生一个异常处理程序(上图中红框对应的字节码序列),这个异常处理程序声明可处理所有的异常,它的目的就是用来执行monitorexit指令。

参考资料

  • 周志明 * 《深入理解Java虚拟机》

相关文章:

  • Java基础之《Ajax+JQuery(JavaEE开发进阶Ⅱ)》—JQuery事件与动画(2)
  • Java日志系列——log4j(1.x版本,已停更,本文仅让大家简单使用和了解)
  • Linux 多线程速度测试
  • 设计模式-策略模式、策略示例、策略模式对比工厂模式
  • 深入探讨go.mod +incompatible
  • maven的进阶学习
  • alluxio简单使用
  • Android基础入门教程——7.1.1 Android网络编程要学的东西与Http协议学习
  • 基于 R 语言的朴素贝叶斯介绍与实践
  • Android初学八之Android网络编程
  • Android 网络编程 记录
  • Vue.js核心技术解析与uni-app跨平台实战开发学习笔记 第13章 uni-app核心基础 13.3 常用特效
  • Oracle索引详解
  • R语言ggplot2可视化:去除可视化结果中的NA图例、删除缺失值图例
  • java生成带logo的二维码
  • [LeetCode] Wiggle Sort
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • cookie和session
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • ES6语法详解(一)
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • javascript 哈希表
  • mysql_config not found
  • Objective-C 中关联引用的概念
  • Octave 入门
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • React-redux的原理以及使用
  • vue自定义指令实现v-tap插件
  • 大主子表关联的性能优化方法
  • 机器学习学习笔记一
  • 基于组件的设计工作流与界面抽象
  • 使用agvtool更改app version/build
  • 数据仓库的几种建模方法
  • 微服务核心架构梳理
  • 微服务入门【系列视频课程】
  • 我的zsh配置, 2019最新方案
  • 转载:[译] 内容加速黑科技趣谈
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • # C++之functional库用法整理
  • #{}和${}的区别是什么 -- java面试
  • #13 yum、编译安装与sed命令的使用
  • #Ubuntu(修改root信息)
  • $L^p$ 调和函数恒为零
  • (1)虚拟机的安装与使用,linux系统安装
  • (3)(3.5) 遥测无线电区域条例
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (区间dp) (经典例题) 石子合并
  • (十六)串口UART
  • (十一)手动添加用户和文件的特殊权限
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • .NET处理HTTP请求