Java虚拟机的指令由操作码(Opcode,代表某种特定操作的数字,长度为1个字节)和操作数(Operands,操作所需的参数)构成。由于Java虚拟机采用面向操作数栈而不是寄存器的指令集架构,所以大多数指令都只有操作码没有操作数。
由于限制了操作码的长度为1个字节,意味着指令集中的操作码总数不能超过256条。又由于Class文件放弃了编译后代码的操作数长度对齐,所以虚拟机在处理长度超过1个字节的数据时,必须在运行时重建数据的结构(如存储16位的无符号整数时,代码中应转变为2个无符号字节的形式存储,byte1<<8|byte2)。
不考虑异常处理的话,Java虚拟机解释器的执行模型如下伪代码所示:
do{
自动计算PC寄存器的值加1;
根据PC寄存器的指示位置,从字节码流中取出操作码;
if(字节码存在操作数){
从字节码流中取出操作数;
}
执行操作码定义的操作;
}while(字节码流长度>0)
1、字节码与数据类型
Java虚拟机指令集中的大部分指令都包含了其操作所对应的数据类型信息。如iload指令用于从局部变量表中加载int类型的数据到操作数栈中,而fload指令加载的则是float类型的数据,这两条指令可能在虚拟机内部由同一段代码实现,但在Class文件中必须拥有各自独立的操作码。
表中列举了指令集中与数据类型相关的字节码指令,通过使用数据类型列所代表的特殊字符替换opcode列指令模板中的T,可以得到具体的字节码指令,单元格为空代表虚拟机不支持对该数据类型执行对应的操作。
大部分指令都不支持byte、char和short类型,甚至没有指令支持boolean类型。编译器会在编译期或运行期将byte或short类型的数据带符号扩展(Sign-Extend)为int类型数据,将boolean或char类型的数据零位扩展(Zero-Extend)为int类型。与之类似,在处理boolean、byte、short和char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理。
2、加载和存储指令
用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,包括如下内容。
将一个数据从局部变量表加载到操作数栈:Tload、Tload_<n>(T=i、l、f、d、a);
将一个数据从操作数栈存储到局部变量表:Tstore、Tstore_<n>(T=i、l、f、d、a);
将一个数据从常量池加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>;
扩充局部变量表的访问索引:wide。
iload_形式的指令将操作数隐含在指令中,不需要进行取操作数的动作,iload_0可以看成操作数为0的iload指令。iload_<n>代表iload_0、iload_1、iload_2、iload_3这4条指令,可以看成针对头4个局部变量的缩写版。
3、运算指令
用于对操作数栈上的两个数据进行运算并将结果存入到栈顶。 由于没有直接支持byte、short、char和boolean类型的运算指令,故使用操作int类型的运算指令替代。
加法:Tadd(T=i、l、f、d);
减法:Tsub(T=i、l、f、d);
乘法:Tmul(T=i、l、f、d);
除法:Tdiv(T=i、l、f、d);
求余:Trem(T=i、l、f、d);
取反:Tneg(T=i、l、f、d);
位移:Tshl、Tshr、Tushr(T=i、l);
或:Tor(T=i、l);
与:Tand(T=i、l);
异或:Txor(T=i、l);
自增:iinc
比较:lcmp、Tcmpg、Tcmpl(T=f、d);
Java虚拟机在进行浮点数运算时,不会抛出任何运行时异常(溢出时返回有符号的无穷大,结果未定义返回NaN),运算结果采取向最接近数舍入模式,将浮点数转换为整数时,采取向零舍入模式。
4、类型转换指令
用于将两种不同的数据类型进行转换。Java虚拟机直接支持(转换时无需显式的转换指令)以下数据类型的宽化类型转换(Widening Numeric Conversions,小范围向大范围类型的安全转换):int类型到long、float或double类型,long类型到float、double类型,float类型到double类型。
处理窄化类型转换(Narrowing Numeric Conversions)时,必须显式地使用转换指令(i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f)完成。窄化类型转换可能导致数据的精度丢失,甚至产生不同正负号、不同数量级的情况。数值类型的窄化转换指令永远不可能导致虚拟机抛出运行时异常。
将int或long类型窄化转换为整数类型T时,直接丢弃除最低位N(T的数据类型长度)个字节以外的内容,可能导致转换结果与输入值有不同的正负号。
将浮点值窄化转换为整数类型T(int或long)时,如果浮点值是NaN,转换结果为T类型的0;如果浮点值不是无穷大,浮点值使用向零舍入模式取整,取整的结果v如果在T的范围之内,转换结果就是v,否则根据v的符号,转换为T能表示的最大或最小正数。
将double类型窄化转换为float类型时,使用最接近数舍入模式得到一个float类型的数字。如果转换结果的绝对值太小而无法用float表示,返回float类型的正负0;如果转换结果的绝对值太大而无法用float表示,返回float类型的正负无穷大;double类型的NaN将转换为float类型的NaN。
5、对象创建与访问指令