JVM-14-字节码指令集

字节码指令集与解析举例

指令集官方文档

概述

  • java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数组(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)构成,由于java虚拟机采用面向操作数栈而不是寄存器的结构,所以大多数的指令都不包含操作数,只有一个操作码

  • 由于限制了java虚拟机操作码的长度为一个字节,即0-255,所以指令集的操作码数不可能超过256条

  • 执行模型:

    java虚拟机的解释器可以使用下面的伪代码当做最基本的执行模型来理解

    image-20201020172219783

  • 字节码与数据类型

    • 在jvm虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息,例如,iload表示从局部变量表加载int型数据到操作数栈
    • 对于绝大多数与数据类型相关的字节码指令,它们的操作码助记符中都有特殊的字符来表明为那种数据类型服务
      • i --》int
      • l --》long
      • s --》short
      • b --》 byte
      • c --》 char
      • f --》 float
      • d --》double
    • 也存在一些指令助记符没有明确指明操作类型的字母,比如arraylength指令,他没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象
    • 还有类似无条件跳转指令goto等是与数据类型无关的
    • 大部分指令都没有支持char、byte、short,甚至没有任何指令支持boolean,编译器会在编译期或运行期将byte、short类型的数据带符号扩展为int类型,将boolean和char类型数据零位扩展为响应的int类型,在处理上述类型数组时,也会使用对应的int类型的字节码指令来处理,因此对于绝大多数char、byte、short、boolean类型数据的操作,实际上都是使用相应的int型数据作为运算类型
  • 指令分类,大致可分为9类

    • 加载与存储指令
    • 算数指令
    • 类型转换指令
    • 对象的创建于访问指令
    • 方法的调用与返回指令
    • 操作数栈管理指令
    • 比较控制指令
    • 异常处理指令
    • 同步控制指令
  • 在做值相关操作时,一个指令可以从局部变量表、常量池、堆中对象、方法调用、系统调用等中取得数据,这些数据被压入操作数栈;一个指令也可以从操作数栈中取出一个到多个值,完成赋值、加减乘除、方法传参、系统调用等操作

加载与存储指令

概述
  • 作用:加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传递
  • 常用指令
    • 局部变量压栈指令:将一个局部变量加载到操作数栈:xloadxload_<n>,其中x为i、l、f、d、a,n为0-3(索引)
    • 常量入栈指令:将一个常量加载到操作数栈,bipushsipushldcldc_wldc2_waconst_nulliconst_m1iconst_<i>lconst_<l>fconst_<f>dconst_<d>
    • 出栈装入局部变量表指令:将一个数值从操作数栈存储到局部变量表,xstore其中x为i、l、f、d、a、b、c、s,xstore_<n>其中x为i、l、f、d、a,n为0-3
    • 扩充局部变量表的访问索引的指令:wide
    • 比如:iload_0就是将局部变量表中索引为0的数据压入栈中
复习:操作数栈和局部变量表
  • 操作数栈

    • 我们知道java字节码是java虚拟机所使用的指令集,因此他与java虚拟机基于栈的计算模型是密不可分的,在解释执行的过程中,每当为java方法分配栈帧时,java虚拟机往往需要开辟一块额外的空间作为操作数栈,来存放计算的操作数以及返回结果

    • 具体一点就是:执行每一条指令之前,java虚拟机要求该指令的操作数已经被压入操作数栈,在执行指令时,java虚拟机会将该指令所需的操作数弹出,并且将指令的结果重新压入栈中

    • 以iadd为例:假设在执行该指令前,栈顶的两个元素分别为int值1,int值2,那么iadd会弹出两个int并将求和得到的int值3压入栈中

    image-20201022150555335

    image-20201022152913789

    • 由于iadd指令只消耗栈顶两个元素,因此对于离栈顶距离为2的元素,即图中的问号,iadd指令并不关心其是否存在,更不会进行修改
  • 局部变量表

    • java方法栈帧的另一个重要组成部分是局部变量区,字节码程序可以将计算的结果缓存在局部变量区之中,实际上,java虚拟机将军变量区当成一个数组,依次存放this指针(仅非静态方法),所传入的参数,以及字节码中的局部变量
    • 和操作数栈一样,long类型以及double类型的值将占据两个单元,其余类型占据一个单元

    image-20201022160550803

    • 比如:

    image-20201022160650356

    • 在栈帧中,与性能调优关系最为密切的部分就是局部变量表,局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收
常量入栈指令

局部变量指令和出栈装入指令都比较简单,这里不做过多解释,主要说明一下常量入栈指令。

常量入栈指令功能是将常量压入操作数栈,根据数据类型和入栈内容的不同,可以分为const系列、push系列和ldc系列

  • const系列:
    • 对于特定的常量入栈 ,入栈的常量隐含在指令本身里面,指令有:iconst_<i>(i从-1-5)、lconst_<l>(l从0-1),fconst_<f>(f从0-2),dconst_<d>(d从0-1),aconst_null
      • iconst_m1,表示将-1压入栈
      • iconst_1,将1压入栈
      • lconst_0,将长整数0压入栈
      • fconst_0,将浮点数0压入栈
      • dconst_0,将double 0压入栈
      • aconst_null,将null压入栈
  • push系列:
    • 主要包括bipush和sipush,区别在于bipush接收8位整数作为参数,sipush接收16位整数作为参数
  • ldc系列:
    • ldc是万能的,可以接收一个8位参数,指向常量池中的int、float、String的索引,将指定内容压入栈
    • 类似的还要ldc_w,接收两个8位参数,能支持的索引范围大于ldc
    • 如果要压入栈的元素是long或double类型,则使用ldc2_w
  • 当常量在const范围内时,使用iconst等压入栈,超出const范围比如int 6,使用push即bipush 6压入栈,其他无法处理的情况使用ldc,各指令范围如下:

image-20201026111026745

  • image-20201026111251309
  • image-20201026111518884

算术指令

  • 作用于两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈
  • 大体上算术指令可以分为两大类:整型运算指令、浮点型运算指令
  • image-20201027153512539
  • 运算时的溢出
    • 数据运算可能会导致溢出,比如很大的两个数相加,结果可能是一个负数,其实java虚拟机并无明确规定过整型数据溢出的具体结果,仅规定了在处理整型数据时,只有除法指令以及求余指令中出现除数为0时会导致虚拟机抛出ArithmeticException
  • 运算模式
    • 向最接近数舍入模式,jvm要求在进行浮点数计算时,所有的运算结果都必须舍入到适当的精读,非精确结果必须舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值一样接近,将优先选择最低有效位为0的
    • 向零舍入模式,将浮点数转换为整数时,采用该模式,该模式将在目标数值类型中选择一个最接近但是不大于原值的数字作为最精确的舍入结果
    • 当除以整数0时,会抛出ArithmeticException,但是当除以0.0时结果将是无穷大
  • NaN值
    • 当一个操作产生溢出时,将会使用有符号的无穷大表示,如果某个操作结果没有明确的数学定义的话,将会使用NaN值来表示,而且所有使用NaN值作为操作数的运算操作,结果都会返回NaN

image-20201027160018598

  • 从字节码角度理解i++和++i
    • ++i
      • image-20201027161539148
    • i++
      • image-20201027161539148
    • 不做赋值运算时,两个的字节码指令是相同的
    • 加入赋值运算后,
      • image-20201027161806486
      • 从2-5行是int j=i++;
      • 从7-10行是 int j=++i;
      • 可以看出int j=i++;是先从局部变量表中加载i的值放到操作数栈中,然后局部变量表中的i值自增,再将操作数栈中为1的值放入j;而int j=++i则是先将局部变量表中i的值自增后,放入到操作数栈,接着赋值给j
    • 对于i=i++;
      • image-20201027162544494
      • 先将10从局部变量表中加载到操作数栈中,然后局部变量表中i自增,此时局部变量表中i=11,接着讲操作数栈中的10赋值给i,最终i的值仍然是10
  • 比较指令说明:
    • 比较指令是比较栈顶两个元素的大小并将结果返回入栈
    • 对于double和float类型的数字,由于NaN的存在,各有两个版本的比较指令,以float为例,有fcmpg和fcmpl两个指令,区别在于在数字比较时遇到NaN后处理结果不同
    • fcmpg遇到NaN时会压入1,而fcmpl会压入-1
    • 数值类型才能比较,boolean、引用数据类型不能比较大小
    • 比如设栈顶元素为v2,第二个元素为v1,如果v1>v2就返回1,v2>v1返回-1,相等返回0

类型转换指令

  • 可以将两个不同的数值类型进行相互转换(除布尔类型之外的基本类型)
  • 这些转换操作一般用于实现用户代码的显示转换,或者用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题
宽化类型转换
  • 就是从小范围类型向大范围类型的安全转换,不需要显示转换
  • 指令有:i2l、i2f、i2d、l2f、l2d、f2d表示从int to long等
  • 精度损失问题
    • 宽化类型转换是不会因为超过目标类型最大值而丢失信息的
    • 但是从int、long向float,或long向double类型进行转换时,有可能发生精读丢失—即可能丢失掉几个最低有效位上的值,转换后的浮点数值是根据IEEE754最接近舍入模式所得到的正确整数值
    • 尽管这种转换有可能发生精读损失,但是不会导致java虚拟机抛出异常

从byte、char和short向int类型的转换实际上是不存在的,对于byte类型转为int,虚拟机并没有做实质性的转化处理,只是简单的通过操作数栈交换律两个数据,而将byte转换为long时使用的是i2l,也就是这里byte已经等同于int类型进行处理。这样做一方面可以减少实际的数据类型,如果为short和byte都准备一套指令,那么指令的数量就会大增,而虚拟机目前的设计上,只愿意使用一个字节来表示指令,因此指令总数不能超过256个,为了节省资源,将short和byte当做int处理,另一方面,由于局部变量表中的槽位固定是32位,无论是byte或者short存入局部变量表,都会占用32位空间,所有也没有必要特别区分这几种数据类型

窄化类型转换
  • 从大范围类型向小范围类型进行转换

  • 对应指令有:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f

  • 精度损失问题:

    • 可能会导致转换结果具备不同的正负号,不同的数量级,因此,转换过程很可能会导致数值丢失精度
    • 同样java虚拟机规范中明确规定数值类型的窄化转换指令永远不肯能导致虚拟机抛出运行时异常

当一个浮点数转换为整数类型int或long时,将遵循以下转换规则:

  • 如果浮点值是NaN,结果就是int或long的0
  • 如果浮点值不是无穷大,使用 IEEE754的向零舍入模式取整,获得整数值v,如果v在目标类型T的表示范围之内,那么结果就是v,否则根据v的符号,转换为T所能表示的最大或者最小正数

当一个double类型窄化转换为float类型时,将遵循以下转换规则

​ 通过向最接近舍入模式舍入一个可以使用float类型表示的数字,最后结果根据以下三条规则判断:

  • 如果转换结果的绝对值太小而无法使用float来表示,将返回float类型的正负零
  • 如果转换结果的绝对值太大而无法使用float来表示,将返回float类型的正负无穷大
  • 对于double类型的NaN值将按规定转换为float类型的NaN值

对象的创建与访问指令

java是面向对象的程序设计语言,虚拟机平台从字节码层面就对面向对象做了深层次的支持,有一系列指令专门用于对象操作,可进一步细分为创建指令,字段访问指令,数组操作指令,类型检查指令

创建指令

虽然类实例和数组都是对象,但java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令

  • 创建类实例的指令
    • new
      • 接受一个操作数,为指向常量池的索引,表示要创建的类型,执行完成后,将对象的引用压入栈
  • 创建数组的指令
    • newarray:创建基本数组类型
    • anewarray:创建引用类型数组
    • multianewarray:创建多维数组
字段访问指令

对象创建后,就可以通过对象访问指令获取对象实例或 数组实例中的字段或者数组元素

  • 访问类字段(static字段,或者称为类变量)的指令:getstatic、putstatic
  • 访问类实例字段(非static字段,实例变量)的指令:getfield、putfield

以getstatic为例,它含有一个操作数,为指向常量池的 fieldref索引,作用就是获取fieldref指定的对象或者值将其压入操作数栈

image-20201105113000149

image-20201105113318764

image-20201105113336410

image-20201105113407451

数组操作指令
  • 把一个数组元素加载到操作数栈的指令:xaload(x为:b,c,s,i,l,f,d,a)
  • 把一个操作数栈的值存储到数组元素中的指令:xastore(x为:b,c,s,i,l,f,d,a)

image-20201105114058675

  • 取数组长度的指令:arraylength
    • 弹出栈顶的数组元素,获取数组的长度,将长度压入栈

说明:

  • 指令xaload表示将数组的元素压栈,执行时要求操作数中栈顶元素为数组索引i,栈顶顺位第二个元素 为数组引用a,该指令会弹出栈顶这两个元素并将a[i]重新压入堆栈
  • xastore则专门针对数组操作,以iastore为例,用于给一个int数组的给定索引赋值,在iastore执行前,操作数栈顶需要以此准备三个元素:值、索引、数组引用,iastore会弹出这三个值,并将值赋给数组中指定索引的位置
类型检查指令

检查类实例或数组类型的指令:instanceof、checkcast

  • checkcast用于检查类型强制转换 是否可以进行,如果可以进行,那么checkcast指令不会改变操作数栈,否则会抛出ClassCastException异常
  • 指令instanceof用来判断给定对象 是否是某一个类的实例,会将判断结果压入操作数栈

方法的调用与返回指令

调用指令
  • invokevirtual
    • 用于调用对象的实例方法,根据对象实际类型进行分派(虚方法分派),支持多态,这也是java中常见的方法分派方式
  • invokeinterface
    • 用于调用接口方法,运行时搜索由特定对象所实现的这个接口方法,并找出适合的方法进行调用
  • invokespecial
    • 用于调用一些需要特殊处理的实例方法,包括实例初始化方法,私有方法和父类方法,这些方法都是静态类型绑定的,不会在调用时进行动态派发
  • invokestatic
    • 用于调用命名类中的类方法(static方法),这是静态绑定的
  • invokedynamic
    • 调用动态绑定的方法,jdk1.7后加入的指令,用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面四条调用指令 的分派逻辑都固话在java虚拟机内部,而这个指令的分派逻辑是由用户所设定的引导方法决定的
返回指令

根据返回值的类型进行区分,可以大致分为两类:

  • ireturn(返回值是boolean,byte、char、short、int时使用),lreturn,freturn,dreturn,areturn
  • 还有一个单独的return是为返回void、实例初始化方法、类和接口的类初始化方法使用

image-20201105135009720

比如:通过ireturn方法,将当前函数操作数栈的顶层元素弹出,并将这个元素压入调用者函数的操作数栈中,所有在当前函数操作数栈中的其他元素都会被丢弃

如果返回的是synchronized方法,还会执行一个隐含的monitorexit指令 ,退出临界区

最后会丢弃当前 方法的整个栈帧,恢复调用者的帧,并将控制权转交给调用者

操作数栈管理指令

jvm提供的可直接操作操作数栈的指令,指令包括如下内容:

  • 将一个或两个栈顶元素从栈顶弹出,并直接废弃:pop,pop2
  • 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup,dup2,dup_x1,dup2_x1,dup_x2,dup2_x2
  • 将栈顶最顶端的两个slot数值位置交换,swap,java虚拟机没有提供交换两个64位数据类型(d,l)的指令
  • 指令nop,特殊指令,字节码为0x00,和汇编语言中的nop一样,表示什么都不做,可用于调试,占位等

说明:

  • 这些指令属于通用型,对栈的压入或弹出无需指明数据类型
  • 不带_x的指令是复制栈顶数据并压入栈顶,包括两个指令dup和dup2,dup的系数代表要复制的slot个数,
    • dup用于复制一个slot的数据,如一个int或一个reference类型数据
    • dup2用于复制两个slot的数据,比如一个long或两个int,或一个int和一个reference
  • 带x的是复制栈顶数据并插入栈顶一下某个位置,共四个指令,插入位置是dup和 x的系数相加
    • 比如dup_x2,就是插入到栈顶三个slot下面
  • pop:将栈顶的一个slot数值弹出
  • pop2:将两个slot数值弹出

控制转移指令

为了支持条件跳转,jvm提供了大量字节码指令,大体上分为以下五种:

比较指令

详见算数指令中的比较指令

条件跳转指令(满足条件跳转,不满足才继续执行)

条件跳转指令通常和比较指令结合使用,在条件跳转指令执行前,一般可以先用比较指令进行栈顶元素的准备,然后进行条件跳转

条件跳转的指令有:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull,这些指令都接受两个字节的操作数,用于计算跳转的位置(16位符号整数作为当前位置的 offset)

image-20201105145227500

  • 与前面运算规则一致
    • 对于boolean、byte、char、short类型的条件分支比较操作,都是使用int类型的比较指令完成
    • 对于long、float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件分支比操作来完成整个分支跳转
  • 由于各类型 的比较最终都会转为int类型的比较操作,所以java虚拟机提供的int类型的条件分支指令是最为丰富和强大的
比较条件跳转指令

比较跳转指令类似于比较指令和跳转指令的结合体,将比较和跳转步骤合二为一

  • 指令:if_icmpeq、if_icmplt、if_icmple、if_icmpne、if_icmpgt、if_icmpge、if_acmpeq、if_acmpne
  • 其中指令助记符加上if_后,以字符i开头的指令针对int型数据(包括 short和char),a表示对象引用的比较

image-20201105171006880

这些指令都接受两个字节的操作数作为参数,用于计算跳转的位置,在执行指令时,栈顶需要准备两个元素进行比较,指令执行完成后,栈顶的这两个元素被清空,且没有任何数据入栈,如果预设条件成立则执行跳转,否则执行下一条语句

多条件分支跳转指令

多条件分支跳转指令是为switch-case语句设计的,主要有tableswitch环绕lookupswitch

image-20201105172836286

区别:

  • tableswitch,要求多条件分支值是连续的,内存只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量的位置,因此效率比较高
  • lookupswitch内部存放各个离散的case-offset对,出于对效率的考虑,会将case-offset对按照case值大小进型排序,每次执行都要搜索全部的case-offset对,找到匹配的case值,并根据对应的offset计算跳转地址,效率较低

image-20201105173351535

image-20201105173405403

无条件跳转指令

目前主要是goto指令,goto指令接收两个字节的操作数,共同组成一个带符号的整数,用于指定指令的便宜量 ,指令执行的目的就是跳转到偏移量给定的位置处

如果指令偏移量太大,超过双子姐的带符号整数的 范围,就可以使用goto_w,接收四个字节的操作数作为参数

指令jsr、jsr_w、ret也是无条件跳转的,多用于try-catch-finally语句,已经被虚拟机逐渐废弃,此处 不再介绍

image-20201105174652221

异常处理指令

image-20201106092941059

抛出异常指令
  • athrow
    • 在java程序中显示抛出遗产的操作都是由athrow指令来实现的,除了使用throw语句显示抛出异常情况之外,jvm规范还规定了许多运行时异常会在其他java虚拟机指令检测到异常状况是自动抛出,例如,在之前介绍的整数运算时,当除数为零时,虚拟机会在idiv或ldiv指令中抛出ArithmeticException异常

正常情况下,操作数栈的压入弹出都是一条条指令完成的,唯一的例外情况是在抛异常时,java虚拟机会清除操作数栈上的所有内容,而后将异常实例压入调用者操作数栈上

异常处理与异常表
  • 在java虚拟机中处理异常不是由字节码指令来实现的(早期使用jsr,ret指令),而是采用异常表来完成的

  • 异常表

    • 如果一个方法定义了一个try-catch或者try-finally的异常处理,就会创建一个异常表,包含了每个异常处理或者finally块的信息,异常表保存了每个异常处理信息,比如:
      • 起始位置
      • 结束位置
      • 程序计数器记录的代码处理的偏移地址
      • 被捕获的异常类在常量池中的索引
  • 当一个异常被抛出时,jvm会在当前的方法里寻找一个匹配的处理,如果没有找到,这个方法会强制结束并弹出当前栈帧,并且异常会重新抛给上层调用的方法,如果在所有栈帧弹出前仍然没有找到合适的异常处理,这个线程将终止,如果这个异常在最后一个非守护线程里抛出,将会导致jvm自己终止,

  • 不管什么时候抛出异常,如果异常处理的最终匹配了所有异常类型,代码就会继续执行,在这种情况下,如果方法结束后没有抛出异常,仍然执行finally块,在return前,直接跳到finally块来完成目标

同步控制指令

java虚拟机支持两种同步结构:方法级同步和方法内部一段指令序列的同步,都是使用monitor来支持的

方法级的同步

是隐式的,即无需通过字节码指令来控制,它实现在方法调用和返回操作之中,虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法

当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否设置

  • 如果设置了,执行线程将先持有同步锁,然后执行方法,最后在方法完成(无论是正常完成还是异常完成)时释放同步锁
  • 在方法执行期间,执行线程有了同步锁,其他任何线程都无法再获得一个同步锁
  • 如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的锁将在异常抛出到同步方法之外时自动释放

image-20201106102008541

说明:

  • 这段代码和普通的无同步操作的代码没有什么不同,没有使用monitorenter和monitorexit进行同步区域控制,这是因为,对同步方法而言,当虚拟机通过方法的访问标识符判断 是一个同步方法时,会自动在 方法调用前进行加锁,当同步方法执行完毕后,不管方法是正常结束还是异常退出,均会由虚拟机释放这个同步锁,因此对于同步方法而言,monitorenter和monitorexit指令是隐式存在的,并未直接出现在字节码中
方法内指定指令序列的同步

同步一段指令集序列:通常由java中的synchronized语句块来表示

当一个线程进入同步代码块时,使用monitorenter指令请求进入,如果当对象的监视器计数器为0,则他会被准许 进入,若为1则判断吃与偶当前监视器的线程是否为自己,是则进入,不是则等待,知道对象的监视器计数器为0,才会被允许进入同步块

当线程退出同步块时。需要使用monitorexit声明退出,在java虚拟机中,任何对象都有一个监视器与之相关联,用来判断对象是否被锁定,当监视器被持有后,对象处于锁定状态

指令monitorenter、monitorexit在执行时,都需要在操作数栈顶压入对象,之后的锁定和释放都是这针对这个对象的监视器 进行的

image-20201106104402566

附录

JVM指令手册

尚硅谷Java研究院

一、栈和局部变量操作
将常量压入栈的指令

aconst_null 将null对象引用压入栈
iconst_m1 将int类型常量-1压入栈
iconst_0 将int类型常量0压入栈
iconst_1 将int类型常量1压入栈
iconst_2 将int类型常量2压入栈
iconst_3 将int类型常量3压入栈
iconst_4 将int类型常量4压入栈
iconst_5 将int类型常量5压入栈
lconst_0 将long类型常量0压入栈
lconst_1 将long类型常量1压入栈
fconst_0 将float类型常量0压入栈
fconst_1 将float类型常量1压入栈
dconst_0 将double类型常量0压入栈
dconst_1 将double类型常量1压入栈
bipush 将一个8位带符号整数压入栈
sipush 将16位带符号整数压入栈
ldc 把常量池中的项压入栈
ldc_w 把常量池中的项压入栈(使用宽索引)
ldc2_w 把常量池中long类型或者double类型的项压入栈(使用宽索引)

从栈中的局部变量中装载值的指令

iload 从局部变量中装载int类型值
lload 从局部变量中装载long类型值
fload 从局部变量中装载float类型值
dload 从局部变量中装载double类型值
aload 从局部变量中装载引用类型值(refernce)
iload_0 从局部变量0中装载int类型值
iload_1 从局部变量1中装载int类型值
iload_2 从局部变量2中装载int类型值
iload_3 从局部变量3中装载int类型值
lload_0 从局部变量0中装载long类型值
lload_1 从局部变量1中装载long类型值
lload_2 从局部变量2中装载long类型值
lload_3 从局部变量3中装载long类型值
fload_0 从局部变量0中装载float类型值
fload_1 从局部变量1中装载float类型值
fload_2 从局部变量2中装载float类型值
fload_3 从局部变量3中装载float类型值
dload_0 从局部变量0中装载double类型值
dload_1 从局部变量1中装载double类型值
dload_2 从局部变量2中装载double类型值
dload_3 从局部变量3中装载double类型值
aload_0 从局部变量0中装载引用类型值
aload_1 从局部变量1中装载引用类型值
aload_2 从局部变量2中装载引用类型值
aload_3 从局部变量3中装载引用类型值
iaload 从数组中装载int类型值
laload 从数组中装载long类型值
faload 从数组中装载float类型值
daload 从数组中装载double类型值
aaload 从数组中装载引用类型值
baload 从数组中装载byte类型或boolean类型值
caload 从数组中装载char类型值
saload 从数组中装载short类型值

将栈中的值存入局部变量的指令

istore 将int类型值存入局部变量
lstore 将long类型值存入局部变量
fstore 将float类型值存入局部变量
dstore 将double类型值存入局部变量
astore 将将引用类型或returnAddress类型值存入局部变量
istore_0 将int类型值存入局部变量0
istore_1 将int类型值存入局部变量1
istore_2 将int类型值存入局部变量2
istore_3 将int类型值存入局部变量3
lstore_0 将long类型值存入局部变量0
lstore_1 将long类型值存入局部变量1
lstore_2 将long类型值存入局部变量2
lstore_3 将long类型值存入局部变量3
fstore_0 将float类型值存入局部变量0
fstore_1 将float类型值存入局部变量1
fstore_2 将float类型值存入局部变量2
fstore_3 将float类型值存入局部变量3
dstore_0 将double类型值存入局部变量0
dstore_1 将double类型值存入局部变量1
dstore_2 将double类型值存入局部变量2
dstore_3 将double类型值存入局部变量3
astore_0 将引用类型或returnAddress类型值存入局部变量0
astore_1 将引用类型或returnAddress类型值存入局部变量1
astore_2 将引用类型或returnAddress类型值存入局部变量2
astore_3 将引用类型或returnAddress类型值存入局部变量3
iastore 将int类型值存入数组中
lastore 将long类型值存入数组中
fastore 将float类型值存入数组中
dastore 将double类型值存入数组中
aastore 将引用类型值存入数组中
bastore 将byte类型或者boolean类型值存入数组中
castore 将char类型值存入数组中
sastore 将short类型值存入数组中
wide指令
wide 使用附加字节扩展局部变量索引

通用(无类型)栈操作

nop 不做任何操作
pop 弹出栈顶端一个字长的内容
pop2 弹出栈顶端两个字长的内容
dup 复制栈顶部一个字长内容
dup_x1 复制栈顶部一个字长的内容,然后将复制内容及原来弹出的两个字长的内容压入栈
dup_x2 复制栈顶部一个字长的内容,然后将复制内容及原来弹出的三个字长的内容压入栈
dup2 复制栈顶部两个字长内容
dup2_x1 复制栈顶部两个字长的内容,然后将复制内容及原来弹出的三个字长的内容压入栈
dup2_x2 复制栈顶部两个字长的内容,然后将复制内容及原来弹出的四个字长的内容压入栈
swap 交换栈顶部两个字长内容

二、类型转换

i2l 把int类型的数据转化为long类型
i2f 把int类型的数据转化为float类型
i2d 把int类型的数据转化为double类型
l2i 把long类型的数据转化为int类型
l2f 把long类型的数据转化为float类型
l2d 把long类型的数据转化为double类型
f2i 把float类型的数据转化为int类型
f2l 把float类型的数据转化为long类型
f2d 把float类型的数据转化为double类型
d2i 把double类型的数据转化为int类型
d2l 把double类型的数据转化为long类型
d2f 把double类型的数据转化为float类型
i2b 把int类型的数据转化为byte类型
i2c 把int类型的数据转化为char类型
i2s 把int类型的数据转化为short类型

三、整数运算

iadd 执行int类型的加法
ladd 执行long类型的加法
isub 执行int类型的减法
lsub 执行long类型的减法
imul 执行int类型的乘法
lmul 执行long类型的乘法
idiv 执行int类型的除法
ldiv 执行long类型的除法
irem 计算int类型除法的余数
lrem 计算long类型除法的余数
ineg 对一个int类型值进行取反操作
lneg 对一个long类型值进行取反操作
iinc 把一个常量值加到一个int类型的局部变量上

四、逻辑运算
移位操作

ishl 执行int类型的向左移位操作
lshl 执行long类型的向左移位操作
ishr 执行int类型的向右移位操作
lshr 执行long类型的向右移位操作
iushr 执行int类型的向右逻辑移位操作
lushr 执行long类型的向右逻辑移位操作

按位布尔运算

iand 对int类型值进行“逻辑与”操作
land 对long类型值进行“逻辑与”操作
ior 对int类型值进行“逻辑或”操作
lor 对long类型值进行“逻辑或”操作
ixor 对int类型值进行“逻辑异或”操作
lxor 对long类型值进行“逻辑异或”操作

浮点运算

fadd 执行float类型的加法
dadd 执行double类型的加法
fsub 执行float类型的减法
dsub 执行double类型的减法
fmul 执行float类型的乘法
dmul 执行double类型的乘法
fdiv 执行float类型的除法
ddiv 执行double类型的除法
frem 计算float类型除法的余数
drem 计算double类型除法的余数
fneg 将一个float类型的数值取反
dneg 将一个double类型的数值取反

五、对象和数组
对象操作指令

new 创建一个新对象
checkcast 确定对象为所给定的类型。后跟目标类,判断栈顶元素是否为目标类 / 接口的实例。如果不是便抛出异常
getfield 从对象中获取字段
putfield 设置对象中字段的值
getstatic 从类中获取静态字段
putstatic 设置类中静态字段的值
instanceof 判断对象是否为给定的类型。后跟目标类,判断栈顶元素是否为目标类 / 接口的实例。是则压入 1,否则压入 0

数组操作指令

newarray 分配数据成员类型为基本上数据类型的新数组
anewarray 分配数据成员类型为引用类型的新数组
arraylength 获取数组长度
multianewarray 分配新的多维数组

六、控制流
条件分支指令

ifeq 如果等于0,则跳转
ifne 如果不等于0,则跳转
iflt 如果小于0,则跳转
ifge 如果大于等于0,则跳转
ifgt 如果大于0,则跳转
ifle 如果小于等于0,则跳转
if_icmpcq 如果两个int值相等,则跳转
if_icmpne 如果两个int类型值不相等,则跳转
if_icmplt 如果一个int类型值小于另外一个int类型值,则跳转
if_icmpge 如果一个int类型值大于或者等于另外一个int类型值,则跳转
if_icmpgt 如果一个int类型值大于另外一个int类型值,则跳转
if_icmple 如果一个int类型值小于或者等于另外一个int类型值,则跳转
ifnull 如果等于null,则跳转
ifnonnull 如果不等于null,则跳转
if_acmpeq 如果两个对象引用相等,则跳转
if_acmpnc 如果两个对象引用不相等,则跳转

比较指令

lcmp 比较long类型值
fcmpl 比较float类型值(当遇到NaN时,返回-1)
fcmpg 比较float类型值(当遇到NaN时,返回1)
dcmpl 比较double类型值(当遇到NaN时,返回-1)
dcmpg 比较double类型值(当遇到NaN时,返回1)

无条件转移指令

goto 无条件跳转
goto_w 无条件跳转(宽索引)

表跳转指令

tableswitch 通过索引访问跳转表,并跳转
lookupswitch 通过键值匹配访问跳转表,并执行跳转操作

异常

athrow 抛出异常或错误。将栈顶异常抛出
finally子句
jsr 跳转到子例程
jsr_w 跳转到子例程(宽索引)
rct 从子例程返回

七、方法调用与返回
方法调用指令

invokcvirtual 运行时按照对象的类来调用实例方法
invokespecial 根据编译时类型来调用实例方法
invokestatic 调用类(静态)方法
invokcinterface 调用接口方法

方法返回指令

ireturn 从方法中返回int类型的数据
lreturn 从方法中返回long类型的数据
freturn 从方法中返回float类型的数据
dreturn 从方法中返回double类型的数据
areturn 从方法中返回引用类型的数据
return 从方法中返回,返回值为void

线程同步

montiorenter 进入并获取对象监视器。即:为栈顶对象加锁
monitorexit 释放并退出对象监视器。即:为栈顶对象解锁

八、JVM指令助记符

变量到操作数栈:iload,iload_,lload,lload_,fload,fload_,dload,dload_,aload,aload_
操作数栈到变量:istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstor_,astore,astore_
常数到操作数栈:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_,lconst_,fconst_,dconst_
加: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,lshr,iushr,lshl,lshr,lushr
按位或:ior,lor
按位与:iand,land
按位异或:ixor,lxor
类型转换:i2l,i2f,i2d,l2f,l2d,f2d(放宽数值转换)
i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f(缩窄数值转换)
创建类实便:new
创建新数组:newarray,anewarray,multianwarray
访问类的域和类实例域:getfield,putfield,getstatic,putstatic
把数据装载到操作数栈:baload,caload,saload,iaload,laload,faload,daload,aaload
从操作数栈存存储到数组:bastore,castore,sastore,iastore,lastore,fastore,dastore,aastore
获取数组长度:arraylength
检相类实例或数组属性:instanceof,checkcast
操作数栈管理:pop,pop2,dup,dup2,dup_xl,dup2_xl,dup_x2,dup2_x2,swap
有条件转移:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull,if_icmpeq,if_icmpene,
if_icmplt,if_icmpgt,if_icmple,if_icmpge,if_acmpeq,if_acmpne,lcmp,fcmpl
fcmpg,dcmpl,dcmpg
复合条件转移:tableswitch,lookupswitch
无条件转移:goto,goto_w,jsr,jsr_w,ret
调度对象的实便方法:invokevirtual
调用由接口实现的方法:invokeinterface
调用需要特殊处理的实例方法:invokespecial
调用命名类中的静态方法:invokestatic
方法返回:ireturn,lreturn,freturn,dreturn,areturn,return
异常:athrow
finally关键字的实现使用:jsr,jsr_w,ret

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值