Java虚拟机-字节码指令
字节码指令
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)构成。Java虚拟机采用面向操作数栈而不是面向寄存器的架构,所以大多数指令都不包含操作数,只有一个操作码。
Java虚拟机操作码长度限制为一个字节(0-255),并且Class文件格式放弃类编译后代码的操作数长度对齐。所以虚拟机处理那些超过一个字节数据的时候,不得不在运行时从字节中重建出具体的数据的结构。这种操作会导致解释执行字节码时损失一些性能,但是这样意味着节省很多填充和间隔符号,编译代码也更加短小精干。这也是因为Java最初设计是面向网络、智能家电的技术背景,并且一直沿用至今。现在的网络带宽相比几十年前已经扩宽了很多倍,本地的计算性能也提高了无数倍。那现在这个思想是否还需要继续保持呢?在56k拨号上网、386的时代Java虚拟机针对编译时的优化或许决定了应用的成败,但是在现在5G和16甚至32核服务器以及Java虚拟机自身的发展与优化,常见的问题主要就是OOM了。
字节码与数据类型
Java虚拟机的指令集中,大多数指令都包含了其操作对应的数据类型,记得刚才说过操作码长度只有一个字节,意味着操作码总数不能超过256条。Java虚拟机支持的数据类型包含8种(byte,short,int,long,float,double,char,reference),大部分指令都不支持byte、short、和char,甚至没有任何指令支持boolean。编译期会在编译期或运行期将byte和short的数据带符号、boolean和char数据零位扩展为int类型。
指令中包含例如 const、load指令,针对int、long、float、double、reference分别为:iconst,lconst,fconst,dconst,aconst;iload、lload、fload、dload、aload。
加载和存储指令
load相关指令将一个局部变量加载到操作栈。
store相关指令将一个数值从操作数栈存储到局部变量表。
push、const相关指令将一个常量加载到操作数栈。
扩充局部变量表的访问索引的指令:wide。
运算指令
运算指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶,大体分为两种:整型数据运行指令与浮点型数据进行运算指令。
所有指令包括:加(add),减(sub),乘(mul),除(div),求余(rem),取反(neg),位移(shl,shr),按位或(or),按位与(and),按位异或(xor),局部变量自增(inc),比较(cmp)
类型转换指令
无需显式转换,宽化类型转换(小范围向大范围的安全转换):int到long、float、double;long到float、double;float到double;
需要显示转换,窄化类型转换:int到byte、char、short;long到int;float到int、long;double到int、long、float;
Java虚拟机规范中明确规定数值类型的窄化转换指令永远不可能导致虚拟机抛出运行时异常。
对象创建与访问指令
创建类实例的指令:new
访问类字段:getfield、putfield、getstatic、putstatic
检查类实例类型:instanceof、checkcast
创建数组:newarray、anewarray、multianewarray
加载数组元素到操作数栈:aload相关
将操作数栈的值存储到数组元素中的指令:astore
取数据长度:arraylength
操作数栈管理指令
将操作数栈顶一个或两个元素出栈:pop,pop2
复制栈顶元素的数值并且压入栈顶:dup、dup_x等相关
栈顶两个数值互换:swap
控制转移指令
控制转移指令可以让Java虚拟机有条件或者无条件从指定位置指令而不是下一条指令继续执行程序。从概念模型上理解,可以认为控制转移指令是在有条件或无条件的修改PC寄存器的值。
条件分支:if相关(ifeq、iflt、ifnotnull等)
复合条件分支:switch(tableswitch,lookupswitch)
无条件分支:goto,jsr(goto、goto_w、jsr、jsr_w、ret)
由于各种数据类型的比较最终都会转换为int进行比较,所以int类型的条件分支指令时最为丰富和强大的。
方法调用和返回指令
invokevirtual用于调用对象的实例方法,最常见的用法。
invokeinterface用于调用接口方法。
invokespecial用于调用特殊处理的实例方法,如实例初始化方法、私有方法、父类方法。
invokestatic用于调用静态方法。
invokedynamic用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。
方法调用指令与数据类型无关,返回指令与数据类型有关。
指令包括:ireturn(返回值时boolean、byte、char、short、int时使用)、lreturn、freturn、dreturn、areturn、return(返回值void,实例初始化方法、类和接口的类初始化方法使用)。
异常处理指令
异常抛出都有athrow指令实现。
处理异常(catch语句)不是有字节码指令来实现的(很久之前曾经使用jsr和ret指令,现在已经不用了),而是采用异常表来完成的。
同步指令
Java中的同步关键字为synchronized语句块,指令集中有monitorenter和monitorexit两条指令支持。