Java虚拟机的指令是由一个字节长度的,代表着某种特定操作含义的数字,称之为操作码,以及
跟随其后的0至多个代表次操作所需的操作数而构成。
操作码的长度为1个字节,因此最大只有256条
基于栈的指令集架构(jvm使用) / 而安卓使用的Dalvik虚拟机是基于寄存器的指令集架构
字节码和数据类型
- 在虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息,如( iload (代表int) 、fload (代表float) 、lload (long) 、dload (double) 、aload (引用数据类型) 等)。
- 大多数指令是包含类型信息的。
- 也有不包涵类型信息的,如( arraylength (虽我们知道是数组类型,但无标注) 、goto (与类型无关) )。
- Java由基本数据类型和引用数据类型组成,而操作码是由一个字节长度组成,即最大只有256条,若每个指令都包含其类型信息,显然是不够的(),对此,Java所提供了一些解决方式:(类型多、指令少的解决方案)。
- 通过转换指令,将一些不支持的类型转换成支持的类型(使用一条转换指令表示多条指令)。
- 字节码指令本身不支持如char、byte、short等基本数据类型(即不包含这些基本数据类型的信息),Java解决方案是将这些不支持的基本数据类型当成int类型来处理,减少了很多的指令。
常用的字节码指令
1、加载和存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。(如果将进行运算等操作需要入栈,将局部变量表中的数据入栈加载入操作数栈中,在操作数栈中运算完毕再将结果出栈存储到局部变量表中)
将局部变量表加载到操作数栈 : iload lload fload dload aload
将一个数值从操作数栈存储到局部变量表 : istore lfda
将一个常量加载到操作数栈 : bipush sipush ldc ldc_w ldc2_w aconst_null iconst_m1 iconst
扩充局部变量表的访问索引的指令 : wide
2、运算指令
运算和算术指令用于对两个操作数栈上的值进行某种特定的运算,并把结果存储到操作数栈顶
- 加法指令 : add
- 减法指令 : sub
- 乘法指令 : mul
- 除法指令 : div
- 取余指令 : rem
- 取反指令 : neg
3、类型转换指令
类型转换指令可以将不同的数值类型进行相互转换,这些转换操作一般用于实现用户代码中的显式类型转换操作以及用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题
宽化类型处理和窄化类型处理
如: l2b 、i2c 、 i2s 、 l2i 等 (2是to l2b 把long型转化为byte类型)
4、对象创建和访问指令
- 创建对象实例的指令 : new
- 创建数组的指令 : newarray anewarray multianewarray
- 访问类字段 : getfiled putfiled getstatic putstatic
- 把数组元素加载到操作数栈的指令 : baload callfda
- 将操作数栈的值存储到数组元素 : astore
- 取数组长度的指令 : arraylength
- 检查实例类型的指令 : instanceof checkcast
5、操作数栈管理指令
- 操作数栈指令用于直接操作操作数栈
- 将操作数栈的一个或两个元素出栈 : pop pop2
- 复制栈顶一个或两个数值并将复制或双份复制值重新压入栈顶: dup dup2 dup_x1 dup_x2
- 将栈顶的两个数值替换 : swap
6、控制转移指令
- 控制转移指令可以让Java虚拟机有条件或无条件的从指定的位置指令而不是控制转移指令的下一条指令继续执行程序。可以认为控制转移指令就是在修改pc寄存器的值
- 条件分支 : ifeq iflt ifle ifgt ifnull ifcmple
- 复合条件分支 : tableswitch lookupswitch
- 无条件分支 : goto goto_w jsr jsr_w ret
7、方法调用指令
- invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。
- invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找到适合的方法进行调用
- invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
- invokestatic指令用于调用类方法(static方法)
8、方法的返回指令
方法的调用指令与数据类型无关,而方法返回指令则是根据返回值的类型区分的,包括有ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法、类和接口的类初始化方法使用
9、异常处理指令
在程序中显示抛出异常的操作会由athrow指令实现,除了这种情况,还有别的异常会在其他Java虚拟机指令检测到异常状态时由虚拟机自动抛出。
10、同步指令
- Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。
- 方法级的同步是隐式的,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中,虚拟机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先持有管程,再去执行方法,最后在方法完成(无论正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获得同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放。
- 同步一段指令集序列通常是由Java语言中的synchronized块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要编译器与Java虚拟机两者协作支持。