在计算机中,CPU指令就是指挥机器工作的指令,程序就是一系列按一定顺序排列的指令,执行程序的过程就是执行指令的过程,也就是计算机的工作过程。
通常一条CPU指令包括两方面的内容:操作码和操作数,操作码表示要完成的操作,操作数表示参与运算的数据及其所在的单元地址(这个单元地址可以是寄存器、内存等)。
Java虚拟机中的字节码指令与CPU中指令类似,Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。
操作数的数量以及长度取决于操作码,如果一个操作数的长度超过了一个字节,那么它将大端排序存储,即高位在前的字节序。
操作码只占一个字节,也就导致操作码个数不能超过256。
class文件只会出现数字形式的操作码,但是为了便于人识别,操作码有他对应的助记符形式。
对于基本数据类型,指令在设计的时候都用一个字母缩写来指代(boolean除外)。
基本数据类型 | 缩写 |
---|---|
byte | b |
short | s |
int | i |
long | l |
float | f |
double | d |
char | c |
refrence | a |
boolean | 无 |
加载存储指令用来交换局部变量表和操作数栈中的数据,以及将常量加载到操作数栈。
iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
其中n为局部变量表中的slot的序号,double和long占用两个slot。
istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
操作数为将要操作的数值或者常量池行号。
运算指令会取出操作数栈栈顶的两个元素进行某种特定的运算,然后将结果重新存入到操作数栈栈顶。
运算指令分为两种:整型运算的指令和浮点型运算的指令。
无论是哪种运算指令,都使用Java虚拟机的数据类型,由于没有直接支持byte、short、char和boolean类型的算术指令,使用操作int类型的指令代替。
加法指令: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、ishr、iushr、lshl、lshr、lushr
按位或指令:ior、lor
按位与指令:iand、land
按位异或指令:ixor、lxor
局部变量自增指令:iinc
比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
类型转换指令可以将两种不同的数值类型进行相互转换。这些转换操作一般用于实现用户代码中的显式类型转换操作,或者用来解决字节码指令集不完备的问题。
宽化指令:
处理窄化类型转换(Narrowing Numeric Conversions)时,必须显式地使用转换指令来完成,这些转换指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。
new:创建对象。
getfield:获取实例对象的属性的值。
putfield:设置实例对象的属性的值。
getstatic:获取类的静态属性的值。
putstatic:设置类的静态属性的值。
newarray:创建元素为基本数据类型的数组。
anewarray:创建数据类型为引用类型的数组。
multianewarray:创建多维数组。
把一个数组元素加载到操作数栈栈顶的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
。
将一个操作数栈栈顶的值存储到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore
。
取数组长度的指令:arraylength
。
instanceof、checkcast。
pop:将操作数栈的栈顶元素出栈。
pop2:将操作数栈的栈顶两个元素出栈。
复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
。
将栈最顶端的两个数值互换:swap
。
控制转移指令可以让Java虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序。
控制转移指令如下:
invokevirtual:指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。
invokeinterface:指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
invokespecial:指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
invokestatic:指令用于调用类方法(static方法)。
invokedynamic:指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面4条调用指令的分派逻辑都固化在Java虚拟机内部,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
方法调用指令与数据类型无关。
方法返回指令是根据方法的返回值类型区分的,包括ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn 和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用。
在Java程序中显式抛出异常的操作(throw语句)都由athrow指令来实现。
monitorenter和monitorexit两条指令来支持synchronized关键字的语义。
public class SynchronizedDemo {
final Object lock = new Object();
void doLock() {
synchronized (lock) {
System.out.println("lock");
}
}
}
对应的字节码如下:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String lock
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
23: aload_2
24: athrow
25: return
Exception table:
from to target type
7 17 20 any
20 23 20 any
在synchronized生成的字节码中,其中包含两条monitorexit指令,是为了保证所有的异常条件,都能够退出。
可以看到,编译后的字节码,带有一个叫Exception table的异常表,里面的每一行数据,都是一个异常处理:
也就是说,只要在from和to之间发生了type异常,就会跳转到target所指定的位置。
public class BoxDemo {
public Integer cal() {
Integer a = 1000;
int b = a * 10;
return b;
}
}
对应的字节码如下:
0: sipush 1000
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: aload_1
8: invokevirtual #3 // Method java/lang/Integer.intValue:()I
11: bipush 10
13: imul
14: istore_2
15: iload_2
16: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
19: areturn
通过观察字节码,我们发现装箱和拆箱的本质:
package com.morris.jvm.bytecode;
public class ArrayDemo {
int getValue() {
int[] arr = new int[]{1111, 2222, 3333, 4444};
return arr[2];
}
int getLength(int[] arr) {
return arr.length;
}
}
getValue()方法对应的字节码如下:
stack=4, locals=2, args_size=1
0: iconst_4
1: newarray int
3: dup
4: iconst_0
5: sipush 1111
8: iastore
9: dup
10: iconst_1
11: sipush 2222
14: iastore
15: dup
16: iconst_2
17: sipush 3333
20: iastore
21: dup
22: iconst_3
23: sipush 4444
26: iastore
27: astore_1
28: aload_1
29: iconst_2
30: iaload
31: ireturn
LineNumberTable:
line 5: 0
line 6: 28
LocalVariableTable:
Start Length Slot Name Signature
0 32 0 this Lcom/morris/jvm/bytecode/ArrayDemo;
28 4 1 arr [I
可以看到,新建数组的代码,被编译成了newarray指令。
数组的创建具体操作:
数组元素的访问,是通过第28~30行字节码来实现的:
getLength()方法对应的字节码如下:
0: aload_1
1: arraylength
2: ireturn
获取数组的长度,是由字节码指令arraylength来完成的。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://morris131.blog.csdn.net/article/details/107819444
内容来源于网络,如有侵权,请联系作者删除!