jvm - 字节码文件

​ java号称编译一次,到处运行;其实真正可以到处运行的是java编译器编译之后的字节码文件同时jvm针对不同平台都做了实现;

​ 除了平台无关性,字节码文件还有语言无关系;比如groovy,它用自己的编译器编译之后生成class文件同样可以运行在jvm之上。

​ 其实我们自己也可以定义一种属于我们自己的语言,然后再写一个编译器,只要编译之后的文件符合class文件的规范,同样可以在jvm上运行。

​ 盗图一张:

“javaè¯­è¨€æ— å…³æ€§â€çš„å›¾ç‰‡æœç´¢ç»“æžœ

class字节码规范

Class 文件是一组以 8 位字节为基础单位的二进制流,其本质就是一张表,如下图所示,u1、u2、u4 表示 1 个字节、2 个字节、4 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 utf-8 编码构成的字符串值。_info 结尾的表示,用以描述有层次关系的复合结构数据。

类型 名称 描述
u4 magic 魔数,用以标记文件类型,固定值0xCAFEBABE
u2 minor_version 次版本号,向上兼容
u2 major_version 主版本号
u2 constant_pool_count 常量的个数
cp_info constant_pool 常量池,数量为(constant_pool_count - 1)
u2 access_flags 访问标识
u2 this_class 当前类索引
u2 super_class 父类索引
u2 interfaces_count 接口的个数
u2 interfaces 具体的接口内容,数量为 interfaces_count
u2 fields_count 字段的个数
field_info fields 具体的字段内容,数量为 fields_count
u2 methods_count 方法的个数
method_info methods 具体的方法内容,数量为 methods_count
u2 attribute_count 属性的个数
attribute_info attributes 具体的属性内容,数量为 attributes_count
常量池(Constant pool)

常量池是Class文件空间最大的数据项之一,长度不固定,主要包含两类:字面量(Literal)和符号引用(Symbolic References),字面量比较接近 Java 语言层面的常量概念,如文本字符串,声明为 final 的常量值等;符号引用这属于编译原理方面的概念,主要包括下面三类常量:

1
2
3
4
5
- 类和接口全限定名(Full Qualified Name)

- 字段的名称和描述符(Descriptor)

- 方法的名称和描述符

Java 是在虚拟机加载 Class 文件的时候进行的动态链接,因此 Class 文件不会保存各个方法字段的最终内存布局信息,关于动态链接部分,将在下一篇文章Java系列(四)——类的加载与执行介绍。

常量池中的每一个数据项都有自己的类型,目前 Java 8一共有以下 14 种表结构数据支持不同的常量类型,具体结构可参见官方文档

常量池中数据项类型 类型标志 类型描述
CONSTANT_Utf8_info 1 UTF-8编码的Unicode字符串
CONSTANT_Integer_info 3 int类型字面值
CONSTANT_Float_info 4 float类型字面值
CONSTANT_Long_info 5 long类型字面值
CONSTANT_Double_info 6 double类型字面值
CONSTANT_Class_info 7 对一个类或接口的符号引用
CONSTANT_String_info 8 String类型字面值
CONSTANT_Fieldref_info 9 对一个字段的符号引用
CONSTANT_Methodref_info 10 对一个类中声明的方法的符号引用
CONSTANT_InterfaceMethodref_info 11 对一个接口中声明的方法的符号引用
CONSTANT_NameAndType_info 12 对一个字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标示方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

可以使用 JDK 提供的 javap 工具,javap -verbose ClassName的方式查看 Class 中的常量。

访问标志(Access flags)

2个字节代表,标示用于识别一些类或者接口层次的访问信息。

标识名 标识值 解释
ACC_PUBLIC 0x0001 声明为public;可以从包外部访问
ACC_FINAL 0x0010 被声明为final;不允许子类修改
ACC_SUPER 0x0020 当被invokespecial指令调用时,将特殊对待父类的方法
ACC_INTERFACE 0x0200 接口标识符
ACC_ABSTRACT 0x0400 声明为abstract;不能被实例化
ACC_SYNTHETIC 0x1000 声明为synthetic;不存在于源代码,由编译器生成
ACC_ANNOTATION 0x2000 声明为注释类型
ACC_ENUM 0x4000 声明为枚举类型
类索引、父类索引和接口索引

类索引(this_class)和父类索引(super_class)都是一个 u2 的数据类型,而接口索引(Interfaces)是一组长度为interfaces_count 的 u2 类型的数据集合,它们的值大多存放的是对常量池中 CONSTANT_Class_info 的引用, Class 文件中由这三项数据来确定这个类的继承关系。

字段表(Field info)

字段表用于描述类或接口中声明的变量,格式如下:

1
2
3
4
5
6
7
field_info {
u2 access_flags; //访问标识
u2 name_index; //名称索引
u2 descriptor_index; //描述符索引
u2 attributes_count; //属性个数
attribute_info attributes[attributes_count]; //属性表的具体内容
}

字段访问标识如下:(表中加粗项是字段独有的):

标识名 标识值 解释
ACC_PUBLIC 0x0001 声明为 public; 可以从包外部访问
ACC_PRIVATE 0x0002 声明为 private; 只有定义的类可以访问
ACC_PROTECTED 0x0004 声明为 protected;只有子类和相同package的类可访问
ACC_STATIC 0x0008 声明为 static;属于类变量
ACC_FINAL 0x0010 声明为 final; 对象构造后无法直接修改值
ACC_VOLATILE 0x0040 声明为 volatile; 不会被缓存,直接刷新到主屏幕
ACC_TRANSIENT 0x0080 声明为 transient; 不能被序列化
ACC_SYNTHETIC 0x1000 声明为 synthetic; 不存在于源代码,由编译器生成
ACC_ENUM 0x4000 声明为enum

Java语法中,接口中的字段默认包含ACC_PUBLIC, ACC_STATIC, ACC_FINAL标识。ACC_FINAL,ACC_VOLATILE不能同时选择等规则。紧跟其后的name_index和descriptor_index是对常量池的引用,分别代表着字段的简单名和方法的描述符。

方法表(Method info)

方法表用于描述类或接口中声明的方法,格式如下:

1
2
3
4
5
6
7
method_info {
u2 access_flags; //访问标识
u2 name_index; //名称索引
u2 descriptor_index; //描述符索引
u2 attributes_count; //属性个数
attribute_info attributes[attributes_count]; //属性表的具体内容
}

方法访问标识如下:(表中加粗项是方法独有的)

标识名 标识值 解释
ACC_PUBLIC 0x0001 声明为 public; 可以从包外部访问
ACC_PRIVATE 0x0002 声明为 private; 只有定义的类可以访问
ACC_PROTECTED 0x0004 声明为 protected;只有子类和相同package的类可访问
ACC_STATIC 0x0008 声明为 static;属于类变量
ACC_FINAL 0x0010 声明为 final; 不能被覆写
ACC_SYNCHRONIZED 0x0020 声明为 synchronized; 同步锁包裹
ACC_BRIDGE 0x0040 桥接方法, 由编译器生成
ACC_VARARGS 0x0080 声明为 接收不定长参数
ACC_NATIVE 0x0100 声明为 native; 由非Java语言来实现
ACC_ABSTRACT 0x0400 声明为 abstract; 没有提供实现
ACC_STRICT 0x0800 声明为 strictfp; 浮点模式是FP-strict
ACC_SYNTHETIC 0x1000 声明为 synthetic; 不存在于源代码,由编译器生成
  • 对于方法里的Java代码,进过编译器编译成字节码指令后,存放在方法属性表集合中“code”的属性内。
  • 当子类没有覆写父类方法,则方法集合中不会出现父类的方法信息。
  • Java语言中重载方法,必须与原方法同名,且特征签名不同。特征签名是指方法中各个参数在常量池的字段符号引用的集合,不包括返回值。当时Class文件格式中,特征签名范围更广,允许方法名和特征签名都相同,但返回值不同的方法,合法地共存子啊同一个Class文件中。
属性表(Attribute info)

属性表的基本结构,不同类型的属性表以此为基础各不相同:

1
2
3
4
5
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}

属性表的限制相对宽松,不需要各个属性表有严格的顺序,只有不与已有的属性名重复,任何自定义的编译器都可以向属性表中写入自定义的属性信息,Java虚拟机运行时会忽略掉无法识别的属性。 关于虚拟机规范中预定义的属性,这里不展开讲了,列举几个常用的。

属性名 使用位置 解释
Code 方法表 方法体的内容
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类、方法表、字段表 声明为deprecated
InnerClasses 类文件 内部类的列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
Signature 类、方法表、字段表 用于支持泛型的方法签名,由于Java的泛型采用擦除法,避免类型信息被擦除后导致签名混乱,Signature记录相关信息
理解:

​ class文件其实就是一种协议或者说是规范,它有自己的格式。java编译器读取java文件,按照class文件的规范生产对应的class文件,jvm根据class文件规范读取class文件之后运行。

解读工具javap

​ 我们自己根据字节码文件的规范,也可以读懂class文件。sun公司其实已经提供了读取class文件的工具javap。

定义类如下:

1
2
3
4
5
6
7
8
public class Student{
public String name;
public int age;

public int add(int a,int b){
return a+b;
}
}

运行javac之后运行javap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
kb-jaydeMacBook-Air:Desktop kb_jay$ javac Student.java
kb-jaydeMacBook-Air:Desktop kb_jay$ javap -verbose Student
Classfile /Users/kb_jay/Desktop/Student.class
Last modified 2019-9-10; size 298 bytes
MD5 checksum fec35654474a4425fb9a2abfbcc72a07
Compiled from "Student.java"
public class Student
minor version: 0 //副版本号
major version: 52 //主版本号
flags: ACC_PUBLIC, ACC_SUPER //类访问标志
Constant pool: //常量池,这里是类文件的资源池
#1 = Methodref #3.#16 // java/lang/Object."<init>":()V
#2 = Class #17 // Student
#3 = Class #18 // java/lang/Object
#4 = Utf8 name
#5 = Utf8 Ljava/lang/String;
#6 = Utf8 age
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 add
#13 = Utf8 (II)I
#14 = Utf8 SourceFile
#15 = Utf8 Student.java
#16 = NameAndType #8:#9 // "<init>":()V
#17 = Utf8 Student
#18 = Utf8 java/lang/Object
{
//字段表,这里的描述符,如果做过ndk,应该很熟悉了
public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC

public int age;
descriptor: I
flags: ACC_PUBLIC

//方法表
public Student();
//方法描述符,ndk开发常用
descriptor: ()V
flags: ACC_PUBLIC
//code是属性表中的一个,
Code:
//操作数栈,局部变量表,参数个数,这里是this
stack=1, locals=1, args_size=1
//字节码指令
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
//LineNumberTable是属性表中的一个,抛出异常的时的异常代码的位置
LineNumberTable:
line 1: 0

public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 6: 0
}
SourceFile: "Student.java"

字节码指令

tip:如下表(转自网络)

字节码 助记符 指令含义
0x00 nop None
0x01 aconst_null 将null推送至栈顶
0x02 iconst_m1 将int型-1推送至栈顶
0x03 iconst_0 将int型0推送至栈顶
0x04 iconst_1 将int型1推送至栈顶
0x05 iconst_2 将int型2推送至栈顶
0x06 iconst_3 将int型3推送至栈顶
0x07 iconst_4 将int型4推送至栈顶
0x08 iconst_5 将int型5推送至栈顶
0x09 lconst_0 将long型0推送至栈顶
0x0a lconst_1 将long型1推送至栈顶
0x0b fconst_0 将float型0推送至栈顶
0x0c fconst_1 将float型1推送至栈顶
0x0d fconst_2 将float型2推送至栈顶
0x0e dconst_0 将double型0推送至栈顶
0x0f dconst_1 将double型1推送至栈顶
0x10 bipush 将单字节的常量值(-128~127)推送至栈顶
0x11 sipush 将一个短整型常量(-32768~32767)推送至栈顶
0x12 ldc 将int,float或String型常量值从常量池中推送至栈顶
0x13 ldc_w 将int,float或String型常量值从常量池中推送至栈顶(宽索引)
0x14 ldc2_w 将long或double型常量值从常量池中推送至栈顶(宽索引)
0x15 iload 将指定的int型本地变量推送至栈顶
0x16 lload 将指定的long型本地变量推送至栈顶
0x17 fload 将指定的float型本地变量推送至栈顶
0x18 dload 将指定的double型本地变量推送至栈顶
0x19 aload 将指定的引用类型本地变量推送至栈顶
0x1a iload_0 将第一个int型本地变量推送至栈顶
0x1b iload_1 将第二个int型本地变量推送至栈顶
0x1c iload_2 将第三个int型本地变量推送至栈顶
0x1d iload_3 将第四个int型本地变量推送至栈顶
0x1e lload_0 将第一个long型本地变量推送至栈顶
0x1f lload_1 将第二个long型本地变量推送至栈顶
0x20 lload_2 将第三个long型本地变量推送至栈顶
0x21 lload_3 将第四个long型本地变量推送至栈顶
0x22 fload_0 将第一个float型本地变量推送至栈顶
0x23 fload_1 将第二个float型本地变量推送至栈顶
0x24 fload_2 将第三个float型本地变量推送至栈顶
0x25 fload_3 将第四个float型本地变量推送至栈顶
0x26 dload_0 将第一个double型本地变量推送至栈顶
0x27 dload_1 将第二个double型本地变量推送至栈顶
0x28 dload_2 将第三个double型本地变量推送至栈顶
0x29 dload_3 将第四个double型本地变量推送至栈顶
0x2a aload_0 将第一个引用类型本地变量推送至栈顶
0x2b aload_1 将第二个引用类型本地变量推送至栈顶
0x2c aload_2 将第三个引用类型本地变量推送至栈顶
0x2d aload_3 将第四个引用类型本地变量推送至栈顶
0x2e iaload 将int型数组指定索引的值推送至栈顶
0x2f laload 将long型数组指定索引的值推送至栈顶
0x30 faload 将float型数组指定索引的值推送至栈顶
0x31 daload 将double型数组指定索引的值推送至栈顶
0x32 aaload 将引用类型数组指定索引的值推送至栈顶
0x33 baload 将boolean或byte型数组指定索引的值推送至栈顶
0x34 caload 将char型数组指定索引的值推送至栈顶
0x35 saload 将short型数组指定索引的值推送至栈顶
0x36 istore 将栈顶int型数值存入指定本地变量
0x37 lstore 将栈顶long型数值存入指定本地变量
0x38 fstore 将栈顶float型数值存入指定本地变量
0x39 dstore 将栈顶double型数值存入指定本地变量
0x3a astore 将栈顶引用类型数值存入指定本地变量
0x3b istore_0 将栈顶int型数值存入第一个本地变量
0x3c istore_1 将栈顶int型数值存入第二个本地变量
0x3d istore_2 将栈顶int型数值存入第三个本地变量
0x3e istore_3 将栈顶int型数值存入第四个本地变量
0x3f lstore_0 将栈顶long型数值存入第一个本地变量
0x40 lstore_1 将栈顶long型数值存入第二个本地变量
0x41 lstore_2 将栈顶long型数值存入第三个本地变量
0x42 lstore_3 将栈顶long型数值存入第四个本地变量
0x43 fstore_0 将栈顶float型数值存入第一个本地变量
0x44 fstore_1 将栈顶float型数值存入第二个本地变量
0x45 fstore_2 将栈顶float型数值存入第三个本地变量
0x46 fstore_3 将栈顶float型数值存入第四个本地变量
0x47 dstore_0 将栈顶double型数值存入第一个本地变量
0x48 dstore_1 将栈顶double型数值存入第二个本地变量
0x49 dstore_2 将栈顶double型数值存入第三个本地变量
0x4a dstore_3 将栈顶double型数值存入第四个本地变量
0x4b astore_0 将栈顶引用型数值存入第一个本地变量
0x4c astore_1 将栈顶引用型数值存入第二个本地变量
0x4d astore_2 将栈顶引用型数值存入第三个本地变量
0x4e astore_3 将栈顶引用型数值存入第四个本地变量
0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置
0x50 lastore 将栈顶long型数值存入指定数组的指定索引位置
0x51 fastore 将栈顶float型数值存入指定数组的指定索引位置
0x52 dastore 将栈顶double型数值存入指定数组的指定索引位置
0x53 aastore 将栈顶引用型数值存入指定数组的指定索引位置
0x54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置
0x55 castore 将栈顶char型数值存入指定数组的指定索引位置
0x56 sastore 将栈顶short型数值存入指定数组的指定索引位置
0x57 pop 将栈顶数值弹出(数值不能是long或double类型的)
0x58 pop2 将栈顶的一个(对于非long或double类型)或两个数值(对于非long或double的其他类型)弹出
0x59 dup 复制栈顶数值并将复制值压入栈顶
0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶
0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5c dup2 复制栈顶一个(对于long或double类型)或两个(对于非long或double的其他类型)数值并将复制值压入栈顶
0x5d dup2_x1 dup_x1指令的双倍版本
0x5e dup2_x2 dup_x2指令的双倍版本
0x5f swap 将栈顶最顶端的两个数值互换(数值不能是long或double类型)
0x60 iadd 将栈顶两int型数值相加并将结果压入栈顶
0x61 ladd 将栈顶两long型数值相加并将结果压入栈顶
0x62 fadd 将栈顶两float型数值相加并将结果压入栈顶
0x63 dadd 将栈顶两double型数值相加并将结果压入栈顶
0x64 isub 将栈顶两int型数值相减并将结果压入栈顶
0x65 lsub 将栈顶两long型数值相减并将结果压入栈顶
0x66 fsub 将栈顶两float型数值相减并将结果压入栈顶
0x67 dsub 将栈顶两double型数值相减并将结果压入栈顶
0x68 imul 将栈顶两int型数值相乘并将结果压入栈顶
0x69 lmul 将栈顶两long型数值相乘并将结果压入栈顶
0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶
0x6b dmul 将栈顶两double型数值相乘并将结果压入栈顶
0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶
0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶
0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶
0x6f ddiv 将栈顶两double型数值相除并将结果压入栈顶
0x70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶
0x71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶
0x72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶
0x73 drem 将栈顶两double型数值作取模运算并将结果压入栈顶
0x74 ineg 将栈顶int型数值取负并将结果压入栈顶
0x75 lneg 将栈顶long型数值取负并将结果压入栈顶
0x76 fneg 将栈顶float型数值取负并将结果压入栈顶
0x77 dneg 将栈顶double型数值取负并将结果压入栈顶
0x78 ishl 将int型数值左移指定位数并将结果压入栈顶
0x79 lshl 将long型数值左移指定位数并将结果压入栈顶
0x7a ishr 将int型数值右(带符号)移指定位数并将结果压入栈顶
0x7b lshr 将long型数值右(带符号)移指定位数并将结果压入栈顶
0x7c iushr 将int型数值右(无符号)移指定位数并将结果压入栈顶
0x7d lushr 将long型数值右(无符号)移指定位数并将结果压入栈顶
0x7e iand 将栈顶两int型数值”按位与”并将结果压入栈顶
0x7f land 将栈顶两long型数值”按位与”并将结果压入栈顶
0x80 ior 将栈顶两int型数值”按位或”并将结果压入栈顶
0x81 lor 将栈顶两long型数值”按位或”并将结果压入栈顶
0x82 ixor 将栈顶两int型数值”按位异或”并将结果压入栈顶
0x83 lxor 将栈顶两long型数值”按位异或”并将结果压入栈顶
0x84 iinc 将指定int型变量增加指定值(如i++, i–, i+=2等)
0x85 i2l 将栈顶int型数值强制转换为long型数值并将结果压入栈顶
0x86 i2f 将栈顶int型数值强制转换为float型数值并将结果压入栈顶
0x87 i2d 将栈顶int型数值强制转换为double型数值并将结果压入栈顶
0x88 l2i 将栈顶long型数值强制转换为int型数值并将结果压入栈顶
0x89 l2f 将栈顶long型数值强制转换为float型数值并将结果压入栈顶
0x8a l2d 将栈顶long型数值强制转换为double型数值并将结果压入栈顶
0x8b f2i 将栈顶float型数值强制转换为int型数值并将结果压入栈顶
0x8c f2l 将栈顶float型数值强制转换为long型数值并将结果压入栈顶
0x8d f2d 将栈顶float型数值强制转换为double型数值并将结果压入栈顶
0x8e d2i 将栈顶double型数值强制转换为int型数值并将结果压入栈顶
0x8f d2l 将栈顶double型数值强制转换为long型数值并将结果压入栈顶
0x90 d2f 将栈顶double型数值强制转换为float型数值并将结果压入栈顶
0x91 i2b 将栈顶int型数值强制转换为byte型数值并将结果压入栈顶
0x92 i2c 将栈顶int型数值强制转换为char型数值并将结果压入栈顶
0x93 i2s 将栈顶int型数值强制转换为short型数值并将结果压入栈顶
0x94 lcmp 比较栈顶两long型数值大小, 并将结果(1, 0或-1)压入栈顶
0x95 fcmpl 比较栈顶两float型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将-1压入栈顶
0x96 fcmpg 比较栈顶两float型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将1压入栈顶
0x97 dcmpl 比较栈顶两double型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将-1压入栈顶
0x98 dcmpg 比较栈顶两double型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将1压入栈顶
0x99 ifeq 当栈顶int型数值等于0时跳转
0x9a ifne 当栈顶int型数值不等于0时跳转
0x9b iflt 当栈顶int型数值小于0时跳转
0x9c ifge 当栈顶int型数值大于等于0时跳转
0x9d ifgt 当栈顶int型数值大于0时跳转
0x9e ifle 当栈顶int型数值小于等于0时跳转
0x9f if_icmpeq 比较栈顶两int型数值大小, 当结果等于0时跳转
0xa0 if_icmpne 比较栈顶两int型数值大小, 当结果不等于0时跳转
0xa1 if_icmplt 比较栈顶两int型数值大小, 当结果小于0时跳转
0xa2 if_icmpge 比较栈顶两int型数值大小, 当结果大于等于0时跳转
0xa3 if_icmpgt 比较栈顶两int型数值大小, 当结果大于0时跳转
0xa4 if_icmple 比较栈顶两int型数值大小, 当结果小于等于0时跳转
0xa5 if_acmpeq 比较栈顶两引用型数值, 当结果相等时跳转
0xa6 if_acmpne 比较栈顶两引用型数值, 当结果不相等时跳转
0xa7 goto 无条件跳转
0xa8 jsr 跳转至指定的16位offset位置, 并将jsr的下一条指令地址压入栈顶
0xa9 ret 返回至本地变量指定的index的指令位置(一般与jsr或jsr_w联合使用)
0xaa tableswitch 用于switch条件跳转, case值连续(可变长度指令)
0xab lookupswitch 用于switch条件跳转, case值不连续(可变长度指令)
0xac ireturn 从当前方法返回int
0xad lreturn 从当前方法返回long
0xae freturn 从当前方法返回float
0xaf dreturn 从当前方法返回double
0xb0 areturn 从当前方法返回对象引用
0xb1 return 从当前方法返回void
0xb2 getstatic 获取指定类的静态域, 并将其压入栈顶
0xb3 putstatic 为指定类的静态域赋值
0xb4 getfield 获取指定类的实例域, 并将其压入栈顶
0xb5 putfield 为指定类的实例域赋值
0xb6 invokevirtual 调用实例方法
0xb7 invokespecial 调用超类构建方法, 实例初始化方法, 私有方法
0xb8 invokestatic 调用静态方法
0xb9 invokeinterface 调用接口方法
0xba invokedynamic 调用动态方法
0xbb new 创建一个对象, 并将其引用引用值压入栈顶
0xbc newarray 创建一个指定的原始类型(如int, float, char等)的数组, 并将其引用值压入栈顶
0xbd anewarray 创建一个引用型(如类, 接口, 数组)的数组, 并将其引用值压入栈顶
0xbe arraylength 获取数组的长度值并压入栈顶
0xbf athrow 将栈顶的异常抛出
0xc0 checkcast 检验类型转换, 检验未通过将抛出 ClassCastException
0xc1 instanceof 检验对象是否是指定类的实际, 如果是将1压入栈顶, 否则将0压入栈顶
0xc2 monitorenter 获得对象的锁, 用于同步方法或同步块
0xc3 monitorexit 释放对象的锁, 用于同步方法或同步块
0xc4 wide 扩展本地变量的宽度
0xc5 multianewarray 创建指定类型和指定维度的多维数组(执行该指令时, 操作栈中必须包含各维度的长度值), 并将其引用压入栈顶
0xc6 ifnull 为null时跳转
0xc7 ifnonnull 不为null时跳转
0xc8 goto_w 无条件跳转(宽索引)
0xc9 jsr_w 跳转至指定的32位offset位置, 并将jsr_w的下一条指令地址压入栈顶

参考:深入理解java虚拟机