Java 指令集概述
一个 JVM 指令由两部分组成,第一部分是一个字节(one-byte)的操作码,第二部分是 0 个或多个提供参数或数据的操作数,许多指令都只有第一部分。
在忽略异常处理的情况下,JVM 解释器通常是在做如下循环:
|
操作数的数量由操作码决定。如果一个操作数的大小大于一个字节,那么使用大端序保存。字节码指令流是单字节(single-byte)对齐的。有两个指令例外:lookupswitch 和 tableswitch,它们被强制为 4 字节(4-byte)对齐。
1. 类型与 JVM
JVM 指令集中的大部分指令编码与它们执行的操作数据类型有关,如: iload 指令读取局部变量的 int 值并压入操作数栈中。fload 指令对 float 类型做了同样的动作。两个指令实现了同样的功能,但是操作码却不同。
对于多数类型指定的指令,指令的类型已经在操作码的字面定义中体现:i 对应 int 操作,l 对应 long,s 对应 short,b 对应 byte,c 对应 char,f 对应 float,d 对应 double,a 对应引用类型。某些类型的指令明确说明类型不在命名中体现,比如 arraylength 指令始终对一个数组对象进行操作。其他一些指令,例如 goto ,一个无条件控制转移,不对类型指定的操作数进行操作。
限定操作数只为一个字节的大小,对 JVM 的指令集的设计也带来了压力。如果每个类型化指令都支持所有Java虚拟机的运行时数据类型,那么就会有比一个字节所能表示的更多的指令。相反,JVM 的指令集为某些操作提供了减少的类型支持。换句话说,指令集是故意不正交的。根据需要,可以使用单独的指令在不支持的和受支持的数据类型之间进行转换。
下面的表格总结了 JVM 指令集支持的类型,如果有些类型对应的操作码是空的,则说明没有支持该类型的指令。比如:int 类型有 iload 指令,而 byte 类型却没有对应的指令。请注意,下面表格中很多操作码都没有与 byte char short 相关的对应,甚至 boolean 类型都没有在表格里。编译器将 byte 和 short 类型通过符号扩展成为了 int,boolean 和 char 通过零扩展成为了 int。也就是说,boolean byte char short 的大多数操作都被当做 int 来对待。
符号扩展:低位数转高位数,在低位数的左边补上低位数的符号位,比如将一个字节扩展为两个字节:0b0000_1010 通过符号扩展成为 0b0000_0000_0000_1010,0b1000_0001 通过符号扩展成为 0b1111_1111_1000_0001。
零扩展:低位数转高位数,在低位数的左边全补 0,比如:0b0000_1010 通过零扩展成为 0b0000_0000_0000_1010,0b1000_0001 通过零扩展成为 0b0000_0000_1000_0001。
| opcode | byte |
short |
int |
long |
float |
double |
char |
reference |
|---|---|---|---|---|---|---|---|---|
| Tipush | bipush | sipush | ||||||
| Tconst | iconst | lconst | fconst | dconst | aconst | |||
| Tload | iload | lload | fload | dload | aload | |||
| Tstore | istore | lstore | fstore | dstore | astore | |||
| Tinc | iinc | |||||||
| Taload | baload | saload | iaload | laload | faload | daload | caload | aaload |
| Tastore | bastore | sastore | iastore | lastore | fastore | dastore | castore | aastore |
| Tadd | iadd | ladd | fadd | dadd | ||||
| Tsub | isub | lsub | fsub | dsub | ||||
| Tmul | imul | lmul | fmul | dmul | ||||
| Tdiv | idiv | ldiv | fdiv | ddiv | ||||
| Trem | irem | lrem | frem | drem | ||||
| Tneg | ineg | lneg | fneg | dneg | ||||
| Tshl | ishl | lshl | ||||||
| Tshr | ishr | lshr | ||||||
| Tushr | iushr | lushr | ||||||
| Tand | iand | land | ||||||
| Tor | ior | lor | ||||||
| Txor | ixor | lxor | ||||||
| i2T | i2b | i2s | i2l | i2f | i2d | |||
| l2T | l2i | l2f | l2d | |||||
| f2T | f2i | f2l | f2d | |||||
| d2T | d2i | d2l | d2f | |||||
| Tcmp | lcmp | |||||||
| Tcmpl | fcmpl | dcmpl | ||||||
| Tcmpg | fcmpg | dcmpg | ||||||
| if_TcmpOP | if_icmpOP | if_acmpOP | ||||||
| Treturn | ireturn | lreturn | freturn | dreturn | areturn |
某些 JVM 指令并不在意类型,比如 pop 和 swap 指令,它们直接操作操作数栈,而无论栈中的数据类型如何。
2. 加载和存储指令
加载和存储指令在栈帧里的局部变量与操作数栈之间进行传递:
- 加载一个局部变量到操作数栈:
iloadiload_<n>lloadlload_<n>floadfloat_<n>dloaddload_<n>aloadaload_<n> - 将操作数栈中的值保存到局部变量中:
istoreistore_<n>lstorelstore_<n>fstorefstore_<n>dstoredstore_<n>astoreastore_<n> - 将一个常量加载到操作数栈:
bipushsipushldcldc_wldc2_waconst_nulliconst_m1iconst_<i>lconst_<i>fconst_<f>dconst_<d> - 使用更宽的索引访问更多的局部变量,或者访问更大的直接操作数:
wide
iload_0和iload的意思都是是将局部变量表中的下标 0 条目加载到操作数栈,istore_2的意思是将操作数栈顶元素弹出并保存到局部变量表下标为 2 的条目。
3. 计算指令
计算指令弹出并计算操作数栈栈顶的两个值,然后将结果压回操作数栈,包括如下指令:
- 加:
iaddladdfadddadd - 减:
isublsubfsubdsub - 乘:
imullmulfmuldmul - 除:
idivldivfdivddiv - 求余:
iremlremfremdrem - 取反:
ineglnegfnegdneg - 位移:
ishlishriushrlshllshrlushr - 按位或:
iorlor - 按位与:
iandland - 按位异或:
ixorlxor - 局部变量递增:
iinc - 比较:
dcmpgdcmplfcmpgfcmpllcmp
4. 类型转换指令
类型转换指令允许 JVM 的两个数字类型进行互转,包括以下:
- 低位数转高位数:
int转long或float或double:i2li2fi2dlong转float或double:l2fl2dfloat转double:f2d
- 高位数转低位数:
int转byteshortchar:i2bi2si2clong转int:l2ifloat转intlong:f2if2ldouble转intlongfloat:d2id2ld2f
5. 对象的创建与操作
虽然类实例和数组都是对象,但是 JVM 创建和操作它们的时候使用了不同的指令集:
- 创建类实例:
new - 创建数组:
newarrayanewarraymultianewarray - 访问类属性(static field):
getstaticputstatic - 访问成员属性:
getfieldputfield - 加载数组中的元素到操作数栈:
baloadcaloadsaloadialoadlaloadfaloaddaloadaaload - 将操作数栈元素保存到数组中某个元素:
bastorecastoresastoreiastorelastorefastoredastoreaastore - 获取数组的长度:
arraylength - 检查实例类型:
instanceofcheckcast
6. 操作数栈管理指令
pop pop2 dup dup2 dup_x1 dup2_x1 dup_x2 dup2_x2 swap
7. 控制转移指令
控制转移指令将使 JVM 的执行转向对应的指令,而不是控制转移指令之后的指令:
- 条件转移:
ifeqifneifltifleifgtifgeifnullifnonnullif_icmpeqif_icmpneif_icmpltif_icmpleif_icmpgtif_icmpgeif_acmpeqif_acmpne - 符合条件分支:
tableswitchlookupswitch - 无条件转移:
gotogoto_wjsrjsr_wret
JVM 在条件转移指令集中区分了 int 和引用类型。boolean byte char short 使用 int 指令集进行比较。long float double 的比较使用《3.计算指令》中描述的比较指令先得到一个 int 结果,再根据 int 结果的分支进行跳转。
8. 方法调用和返回指令
由下面 5 中指令来调用方法:
invokevirtual:调用对象实例的一个指定方法,这是 Java 语言中常见的方法调用;invokeinterface:调用接口方法,搜索运行时实现了该接口方法的对象,并调用;invokespecial:调用特殊方法,比如实例化方法<init>和<clinit>、私有方法或超类中的方法;invokestatic:调用类的静态方法;invokedynamic:调用Lambda动态绑定方法。
方法的返回指令分别为:ireturnlreturnfreturndreturnareturnreturn。
9. 抛出异常
athrow
10. 同步 指令
使用 synchronized 修饰的语句块:monitorenter monitorexit
最后更新: