十六进制编辑器(手机16进制编辑器)
?表 2.2.6-11 一系列字节码指令代表的含义?
此属性用于保存 invokedynamic 指令引用的引导方法限定符。
17
04 ~ 05:次版本号(Minor Version)0x0000。
05
7: ireturn // 方法结束,返回操作数栈顶数 1
u2
将第 1 个变量槽中为引用类型的本地变量推送至栈顶
iconst_1
描述
0x2000
1
// Method java/lang/Object.&34;<init>&34;:()V
00
[1] 若是接口类型,父类索引对应的全限定名为 java/lang/Object,接口索引对应的才是此接口继承的父接口全限定名。
30
在 Java 代码层面上的方法特征签名只包括方法名称和参数的个数、类型、顺序;而在字节码中的特征签名还包括方法返回值及受检异常表,方法的描述符包括参数列表和返回值。
1
0x12
1FA ~ 1FB:0x0001,属性个数为 1;
此索引指向一个 CONSTANT_Class_info 型的常量
综合,此字段为 int i;。
00
[4] 表面上无参,其实在任何实例方法里,都可以通过 “this” 关键字访问到此方法所属的对象。
表示一个动态方法调用点
java
1
接着是下一个方法表;
1
0x0200
1C5 ~ 1C6:0x0003,源码行号 3。
06
u2
[3] 受篇幅原因,其余类型常量的结构,可在 《Java 虚拟机规范》的 4.4 节查看。
如果存在异常表,则结构如下:
u4
03
如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错代码所属的文件名。
14: istore_1 // 将 3 取出,存储到第 2 个变量槽 {} {this, 3, e, 2}
Code:
// x = 3;
访问变量,必须通过指向 CONSTANT_Fieldref_info 的索引,定位到对象地址,再获取此对象的某个字段值。
数量
ACC_SYNCHRONIZED
0C
字段由编译器自动生成
0A
00
数量
综合,此字段为 String name;。
end_pc
// 将 1 重新读到操作数栈顶,准备返回
处理位置
标志位 1
u2
minor version: 0
3: ldc
1
ACC_SYNIHETIC
u2
I = 4;
类型
方法特征签名最重要的任务就是作为方法独一无二不可重复的 ID。重载(Overload)即方法名相同,但特征签名不同。
6
Exception in thread &34;main&34; java.lang.UnsupportedClassVersionError:
?码 2.2.7-4 变量赋值?
attribute_name_index
}
16
类型
length
00
ACC_SUPER
u2
1D7 ~ 1D8:0x0001,局部变量表的存储空间为 1。
对于数组类型,每一维用一个前置的 “[” 字符表示,如 “java.lang.String[][]” 类型的二维数组的描述符为 “[[Ljava/lang/String;”,“int[]” 类型的一维数组的描述符为 “[I”。
LocalVariableTable 属性
ldc 指令的参数,代表某字面量;此时推送的是第 3 项常量:“java”
...
line 1: 0
u2
this_class
UTF-8 编码的字符串
5
CONSTANT_Utf8_info
00
07
1
字段或方法的部分符号引用
00000000:
Exception table:
public void some() {
0C
[1] 一个字节 8 位,每位都是 0 或 1,例 11001010,转为十六进制显示:CA。常常加上前缀表明进制,如 0xCA 代表按十六进制显示的一个字节。
?表 2.2.2-4 CONSTANT_Fieldref_info 型常量的结构?
名称
} catch (Exception e) {
1: invokespecial 1
Code:
类型
字段表
接口中的方法的符号引用
01
00
super_class
CONSTANT_Module_info
由于常量池中,常量的数量不固定,需要一项 u2 类型的数据,记录下数量;
u2
JDK5 引入泛型后,新增属性 LocalVariableTypeTable,仅将 descriptor_index 替换了字段的特征签名(Signature)[1],signature_index 是一个索引,指向 CONSTANT_Utf8_info 型常量。
名称
00
invokespecial
u4
4
0x1000
0D ~ 0E:0x0016,十进制为 22,指向第 22 项常量,第 22 项常量属于...略。
attribute_name_index
0x12
01
stack=1, locals=5, args_size=1
2A ~ 2B:0x001F,十进制为 31,指向第 31 项常量。
0x0040
tag
09
UTF-8 编码的字符串占用字节数
调用栈顶引用类型的数据的实例方法
5: iconst_1
00
float
字节码
32
0B
00
// --- finally ---
0A
00
// java/lang/System
名称
0x0001
u2
04
00
25 = NameAndType
flags:
flags: ACC_FINAL, ACC_SUPER
将 int 类型的 1 推送至栈顶
ldc
数量
0E
21 = Utf8
?码 2.2.5-1 class 反编译后的字段表内容?
189 ~ 18A:方法访问标志,识别方法的修饰符,0x0001,此方法是 public 的。
// --- catch ---
含义
C
4: iconst_3 // 将 int 类型的 3 复制到操作数栈顶 {3} {this, 1, 1}
6E
I
0x0010
1
// out:Ljava/io/PrintStream;
[1] 使用魔数作为文件格式的标识,因为扩展名可以随意改动,不安全。文件格式的制定者可以自由地选择魔数值,只要这个魔数值还没有被广泛采用过而且不会引起混淆。
00
标志位 7
数量
名称
0A
interfaces
18 = Utf8
4
156 ~ 157:0x0015,此字符串字面量占用 21 个字节。
描述
整型字面量
[2] 虽然它是一个 u4 类型的长度值,理论上最大值可以达到 232,但是《Java虚拟机规范》中明确限制了一个方法不允许超过 65535 条字节码指令,即它实际只使用了 u2 的长度,如果超过这个限制,javac 编译器就会拒绝编译。
u2
fields_count
abstract
1
// --- finally ---
exception_table_length
不超过 32 位的数据类型:byte、char、float、int、short、boolean、returnAddress 等,每个局部变量占用一个变量槽。
0D
00000180:
69
bootstrap_method_table
00
179 ~ 17A:字段名索引(name_index),用于确定字段名;0x000A,指向第 10 项常量;第 10 项常量是 CONSTANT_Utf8_info 类型,它的 bytes 经解码后为 i,所以此字段名为 i。
0xB7
被模块导出或者开放的包(Package)类和接口的全限定名(Fully Qualified Name)字段的名称和描述符(Descriptor)方法的名称和描述符方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)class 文件中不会保存各个方法、字段最终在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。
u2
魔数与版本号常量池访问标志类索引、父类索引、接口索引集合字段表集合方法表集合属性表集合java 文件经 javac 编译器编译后生成 class 文件。
0F
0A:第 1 项常量的标志位 0x0A,十进制为 10;查表可知这个常量属于 CONSTANT_Methodref_info 类型,代表类中方法的符号引用。
1B1 ~ 1B2:0x0001,Code 的属性数量为 1。
0x0080
public Happy();
00
java/lang/Object
9: iconst_2 // 将常量 2 复制到操作数栈顶 {2} {this, ?, e}
methods
1B3 ~ 1B4:属性名称的索引(attribute_name_index),0x0011,指向第 17 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 “LineNumberTable”,此属性描述 Java 源码行号与字节码行号(字节码的偏移量)之间的对应关系。
1C1 ~ 1C2:0x0002,源码行号 2。
00
aload_0
00
attribute_length
length
line_number_table_length
<init>
00
00
类型
00
synchronized
61
此索引指向 CONSTANT_Class_info 类型的常量,表示方法所属的类型
这是 putfield 指令的参数,代表一个符号引用,指向第 4 项 CONSTANT_Fieldref_info 型常量:Happy.name:Ljava/lang/String;,应为 Happy 类的 String 类型的变量名为 name 的变量赋值
number_of_exceptions:方法声明抛出的异常的数量
?表 2.2.5-2 字段表结构?
00
fields_count
标识字符
00
// 将 1 重新读到操作数栈顶,准备返回
class 文件是一组以 8 个字节为基础单位的二进制流[1],只有两种数据类型:无符号数、表。
09
0: iconst_1 // 将 int 类型的常量 1 加载到操作数栈顶 {1} {this}
return
1
com/cqh/arr3/FieldResolution has been compiled by a more recent version of the Java Runtime (class file version 60.0),
18F ~ 190:0x0001,方法的属性数量为 1。
05
methods_count
00000160:
0x0004
line_number_info
interfaces_count
1B5 ~ 1B8:0x0000000E,属性值长度为 14;从 1B9 ~ 1C6 都是此属性的内容。这个属性表的长度为 14 + 6 = 20 个字节,从 1B3 ~ 1C6。
00
fields
00
00
1
local_variable_table_length
J
Ljava/lang/String;
Hello World
02
0A
flags: ACC_PUBLIC, ACC_STATIC
0x0004
如果不选择生成 LineNumberTable 属性,当抛出异常时,堆栈中将不会显示出错的行号;在调试程序时,也无法按照源码行来设置断点。
5: invokevirtual 7
1
含义
00
num_bootstrap_methods
1
为栈顶数据指向的对象的实例变量赋值(显式初始化)
15
1CB ~ 1CC:0x0013,此方法的描述符索引,指向第 19 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 ([Ljava/lang/String;)V,一个返回值类型为 void,形参为 String[] 类型的方法。
53
00000190:
01
u2
0C
?码 2.2.7-1 声明时不赋值?
0x0005
CONSTANT_Package_info
17 = Utf8
000001A0:
类文件
15
attribute_length
ConstantValue 属性
14:15
exception_index_table
1 = Methodref
8.23
1
0x0001
描述
开始位置(相对于方法体的位置,下同)
4 // 将第 5 个变量槽的值复制到操作数栈顶 {e} {this, 3, ?, ?, e}
09
1CF ~ 1D0:0x0010,属性名称的索引,指向第 16 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 “Code”。
0E
B
使用位置
6: putfield
2: iload_1 // 将局部变量表的第 2 个变量槽中的值复制到操作数栈顶 {1} {this, 1}
start_pc
u2
UTF-8 编码的字符串
u4
I
name
1E6 ~ 1E7:异常表长度,0x0000,即方法抛出的异常个数为 0。
01
0A
ACC_STRICT
?表 2.2.7-8 bootstrap_method_table 结构?
// --- try ---
access_flags
0: getstatic
05
...
指向 CONSTANT_MethodHandle_info 型常量
u4
15 ~ 16:0x0018,指向第 24 项常量。
00
00
属性名称
E9:0x01,第 31 项常量的标志位,属于 CONSTANT_Utf8_info 类型。
调用栈顶数据所指向的对象的实例初始化方法、父类方法、私有方法,后跟一个 u2 类型的参数说明调用哪个方法
0x0001:引导方法数量为 1。
00
u2
int i1 = 3;
// --- finally ---
descriptor: ()I
01
02
putfield
1BD ~ 1BE:0x0001,源码行号 1。
56
04
01
例举方法声明抛出的异常,位于方法表结构的属性表中。
00
int i2 = i1;
1
187 ~ 188:方法数量(methods_count),0x0002,类型中声明了 2 个方法。
u2
u2
bootstrap_method_info
Code:
Address
u1
?表 2.2.6-10 line_number_info 结构?
00
B7
32 = Utf8
attribute_info
02
表示一个模块
u2 类型
标志位 9
15: return
u1
名称
ACC_VARARGS
29 = Class
Code
1A
类型
28
out
00
1
descriptor: Ljava/lang/String;
2F
u2
此索引指向 CONSTANT_Utf8_info 类型的常量,表示字符串字面量
8
...
28 = Utf8
1
00
1
name_index
03
数量
21: aload
handler_pc
0C
0A
u1
11 = Utf8
表示方法类型
0x0008
02
01
6E
// 将异常 e 重新读到操作数栈顶,准备上抛给调用者
00
attributes 是 attribute 的集合,用来存储一些额外的信息,如 java 代码编译成的字节码指令、final 常量值、方法抛出的异常列表...
1
static
descriptor_index
符号引用主要包括下面几类常量:
line_number_table_length
标志名称
1F2 ~ 1F3:0x0000,字节码行号 0。
1F8 ~ 1F9:0x0006,源码行号 6。
25
名称
标志名称
exception_table_length
1B
exception_info
数量
05
分别为:魔数、次版本号、主版本号、常量数量、常量表的集合、访问标志、类索引、父类索引、接口索引集合、字段数量、字段表集合、方法数量、方法表集合、属性数量、属性表集合。
num_bootstrap_arguments
00
int
27
attributes_count
00 ~ 03:0xCAFEBABE,这 4 个字节称为魔数(magic),用来确定此文件是否为一个能被虚拟机接受的 class 文件。[1]
名称
ACC_SYNIHETIC
08
ACC_PROTECTED
所以 ()V 代表此方法没有形参,返回值类型为 void。
00
再回过头来;
02
00
int i2 = i1;
类型
u2
00
1
类型
public int some() throws Exception {
4 = Fieldref
数量
06
00000150:
00
EnclosingMethod
3
1
09
CONSTANT_Methodref_info
// Field i:I
描述
数量
attribute_length
枚举类型
1
16 = Utf8
00
29.30
u2
B5
内部类列表
名称
00
00
名称
00
含义
0x0100
1
1
1
1
// Hello World
00
24 = Utf8
05
0A
00
u4
ACC_PROTECTED
ACC_ENUM
1EA ~ 1EB:0x0011,属性名称的索引,指向第 17 项常量:“LineNumberTable”。
attributes_count
20
2 个字节能够表示的最大值为 65535,占用字节数最大不能超过 65535,即要小于 64 KB,否则无法通过编译。
00
1
1F4 ~ 1F5:0x0005,源码行号 5。
13: iconst_3 // 将常量 3 复制到操作数栈顶 {3} {this, 2, e, 2}
类或接口的符号引用
?表 2.2.2-1 常量池?
00
方法抛出的异常列表
[3] code 存储源代码编译后生成的字节码指令。每个字节码指令就是一个 u1 类型的单字节,当虚拟机读取到 code 中的一个字节时,就可以对应找出这个字节代表的是什么指令,并且可以知道这条指令后面是否需要跟随参数,以及后续的参数应当如何解析。
1
1FC ~ 1FD:0x0014,属性名称的索引(attribte_name_index),指向第 20 项 CONSTANT_Utf8_info 类型的常量:“SourceFile”,此属性用于记录生成 class 文件的源码文件名称。
00
09
12
00
flags: ACC_PUBLIC
u2
?表 2.2.6-6 Code 属性结构?
34:35
07
i
tag
name_and_type_index
ACC_ABSTRACT
0F
catch_type
00
02
num_bootstrap_arguments
00
000001C0:
表示一个动态计算常量
19
191 ~ 192:属性名称的索引(attribute_name_index),0x0010,指向第 16 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 “Code”,Code 属性是方法体中的代码的字节码描述。
line_number
0D
stack=2, locals=1, args_size=1
// java/io/PrintStream
u2
0
string_index
2F
09
line 5: 0
08
double
u2
major_version
1B9 ~ 1BA:0x0003,行号表长度为 3。
00
println
描述栈帧中局部变量表的变量与源码定义的变量之间的关系,在 Code 属性的属性表中。
u2
// String Hello World
局部变量的作用域在 [start_pc, start_pc + length) 内。
u2
6 = String
注解类型
0x0400
1
BE
u2
1
02
exception_table
int x;
CONSTANT_Integer_info
08
1
access_flags
158 ~ 16C:0x284C6A6176612F6C616E672F537472696E673B2956,经 UTF-8 解码,得 (Ljava/lang/String;)V。
04
8: astore_2 // 将异常取出,存储到第 3 个变量槽中 {} {this, ?, e}
tag
EA ~ EB:0x0005,此字符串字面量占用 5 个字节。(每个英文字符占用 1 个字节)
175 ~ 176:字段数量(fields_count),0x0002,类型中声明了 2 个字段。
每个属性应满足如下结构:
?表 2.2.5-4 描述符字符含义?
名称
?表 2.2.7-7 BootstrapMethods 属性结构?
类型
CONSTANT_Float_info
此索引指向 CONSTANT_Class_info 类型的常量,表示字段所属类型
183 ~ 184:0x000D,此字段的描述符索引,指向第 13 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 “Ljava/lang/String;”,此字段的类型为 java.lang.String。
local_variable_table_length:局部变量个数。
start_pc:局部变量开始的字节码偏移量
00
public
0x0001 的二进制为 0000 0000 0000 0001,若类是 public 类型,则第 1 位应为 1,否则为 0。
1FE ~ 1E1:0x00000002,属性值长度(attribute_length)为 2。
67
2: return?表 2.2.7-6 ConstantValue 属性结构?
1
java/io/PrintStream
8 = Class
08
表 2.2-1 class 文件结构?
26 = Class
CONSTANT_Fieldref_info
19F ~ 1AE:存储字节码指令的一系列字节流(code)[3],0x2AB700012A04B500022A1203B50004B1,每个字节码代表的指令:
length
}public int some() throws java.lang.Exception;
26.27
长整型字面量
数量
23: athrow // 抛出异常
ACC_PUBLIC
000001F0:
接下来是字段表(field_info)结构,用于描述字段。
1E8 ~ 1E9:0x0001,属性数量为 1。
0F
ConstantValue
类型
数量
u2
08
u2
public void some() {
CONSTANT_MethodHandle_info
类型
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量即文本字符串、被声明为 final 的常量值等。
00
被 final 修饰的字段,在声明时使用字面量的方式赋值,field_info 结构的属性表中会生成此项属性,目的是通知 JVM 自动为类变量赋值。[2]
0xB5
SourceFile
} finally {
?表 2.2.6-2 方法表结构?
04
12
方法体里面的代码经过 javac 编译后,最终变为字节码指令存储在 Code 属性内,结构如下:
?表 2.2.2-5 CONSTANT_String_info 型常量的结构?
04
05
00
main
?表 2.2.7-2 SourceFile 属性结构?
00
1E
CA
// Happy.i:I
30 = NameAndType
00
这个类型并非由用户代码生成
1BB ~ 1BC:0x0000,字节码行号 0。
1
8: return
0E
bootstrap_argument
索引指向某项常量
[2] 解析阶段的前期绑定与后期绑定。
05
17D ~ 17E:属性数量(attributes_count),0x0000,这里为 0,自然就没有属性表了。
00
Address
13
0xB1
code_length
17 any若 try 中没有抛出异常,返回 1;若 try 中抛出的异常可被 catch 捕获,跳转至 8:,返回 2;若抛出的异常没有被捕获,跳转至 17,将异常上抛给调用者;无论是否抛出异常,finally 都会执行。
descriptor_index:局部变量的描述符索引
[2] 如果 field_info 结构表示的非静态字段(如实例变量)包含了 ConstantValue 属性,那么这个属性必须被虚拟机所忽略。
attributes
u1
0x003E:转十进制为 62,指向第 62 项 CONSTANT_MethodHandle_info 型常量,REF_invokeStatic com/cqh/arr3/Test.lambda$main$0:(I)V 调用 Test 类型的 lambda... 方法。
13 = Utf8
u2 类型
Z
描述
ACC_FINAL
14 = Utf8
方法表
attributes_count
00
?表 2.2.2-2 常量类型?
name_index
ACC_PUBLIC
u2
0B
u1
cp_info
00
Code
185 ~ 186:0x0000,此字段没有多余的属性信息。
attribute_length
ACC_SYNIHETIC
标志
01
final static String STR = new String(&34;Hello&34;);也不会有 ConstantValue 属性,在 <clinit> 方法中赋值。
01
3B
紧接着是 Code 属性的属性;
12 ~ 13:0x0017,指向第 23 项常量。
05
00
1
Ljava/io/PrintStream;
由 final 关键字定义的常量值
0x0009,指向第 9 项常量;第 9 项常量属于 CONSTANT_Class_info 类型,它的 name_index 指向第 32 项常量,第 32 项常量属于 CONSTANT_Utf8_info 类型,它的 bytes 经 UTF-8 解码后,为 java/lang/Object。[2]
0C
Error: A JNI error has occurred, please check your installation and try again
例:JDK1 无法执行版本号为 46 及以上的 class 文件。
CONSTANT_String_info
接下来是类文件的属性。
u2
方法表
表示方法句柄
tag
?码 2.2.2-1 常量表集合?
08 ~ 09:常量的数量(constant_pool_count),0x0027,十进制为 39,代表常量池中有 38 项常量,索引值为 1 ~ 38。[1]
attribute_name_index
1F
17B ~ 17C:字段的描述符索引(descriptor_index),代表字段的描述符,用来描述字段的数据类型。
u2
1D5 ~ 1D6:0x0002,操作数栈的深度的最大值为 2。
1
31 = Utf8
attribute_length
?码 2.2.7-3 非字面量形式赋值:调用方法?
descriptor_index
Constant pool:
1
描述
38 = Utf8
final
5
字段的符号引用
2A
16F ~ 170:0x0008 这 2 个字节称为类索引(this_class),用于确定此类的全限定名,指向第 8 项常量。
这段字节码代表的含义:调用 Object 的无参构造方法,为 this 指向的对象的 int 类型的实例变量 i 赋值 1,为 this 指向的对象的 String 类型的实例变量 name 赋值 “java”,然后方法结束,返回值为 void。
0x0002
?表 2.2.4-2 CONSTANT_Class_info 型常量的结构?
00
max_stack
17: astore
01
u2
00
public
因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留第一个变量槽来存放对象实例的引用。
11
01
index
line 3: 9
描述
SourceFile: &34;Happy.java&34;[1] 并不是在方法中用了多少个局部变量,就把这些局部变量所占变量槽数量之和作为 max_locals 的值。
将第 1 个变量槽中为引用类型的本地变量推送至栈顶
29
16
1
0x0002
类中方法的符号引用
return x;
?表 2.2.6-4 属性结构?
BootstrapMethods 属性
将局部变量表中的第 1 个变量槽中的引用类型的本地变量推送至栈顶[4]
02
由编译器产生的桥接方法
02
?码 2.2.7-2 非字面量形式赋值:调用构造器?
// x = 2;
u1 类型
Address
10
00
10 ~ 11:0x0008,指向第 8 项常量。
00
类型
数量
00
18
29:0x07,十进制为 7,第 8 项常量的标志位,属于 CONSTANT_Class_info 类型。
4: aload_0
1
u2
int i;
Address
0x0010
CONSTANT_MethodType_info
将 int、float、String 型常量值从常量池中复制到操作数栈顶
static {
ACC_FINAL
u2 类型
属性长度,不包括开始的 6 个字节
6E
33 = Utf8
// Happy.name:Ljava/lang/String;
0B ~ 0C:0x0009,指向第 9 项常量,第 9 项常量属于 CONSTANT_Class_info 类型,此常量的 name_index 值为 32,指向第 32 项常量,第 32 常量属于 CONSTANT_Utf8_info 类型,此常量的 bytes 值经 UTF-8 解码后,为 “java/lang/Object”。所以此方法所属类型已然确定。
17F ~ 180:0x0000,此字段无修饰符。
0E
00
1
33
类型
引导方法表
03
1C3 ~ 1C4:0x0009,字节码行号 9。
u2
0F:第 2 项常量的标志位 0x09,十进制为 9,查表可知这个常量属于 CONSTANT_Fieldref_info 类型,代表字段的符号引用。
}0: iconst_3
0F
从当前方法返回 void
数量
12
00000010:
第 2 项常量是字段的符号引用,说明此字段在 Happy 类中,变量名为 i,int 类型。
0x04
13
00000020:
00
1EC ~ 1EF:0x0000000A,属性值长度为 10:1EF ~ 1F8。
attribute_info
69
return x;
01
1F6 ~ 1F7:0x0008,字节码行号 8。
5 = Fieldref
02
u2
19
field_info
使用 invokespecial 字节码指令的新语义,JDK 1.0.2 后都为真
在 javac 编译时,把对 this 关键字的访问转变为对一个普通方法参数的访问,在 JVM 调用实例方法时自动传入此参数。
00
final
2
attribute_length
protected
10: istore_1 // 将 2 取出,存储到第 2 个变量槽 {} {this, 2, e}
4C
1
10: ldc
constantvalue_index:此索引指向常量池中一个字面量常量,根据字段类型不同,可以是 CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、 CONSTANT_Integer_info和CONSTANT_String_info 常量中的一种。
00
00
u2
15 = Utf8
引导方法的数量
?图 2.2.4-1 类索引的查找过程?
04
1
0D
00000160:
接受可变长参数
ldc
constant_pool_count-1
name_index
0B
标志名称
0x0020 的二进制为 0000 0000 0010 0000,若类使用 invokespecial 指令的新语义,则第 6 位应为 1,否则为 0。
constantvalue_index
00
从当前方法返回 void
00
num_bootstrap_methods
protected
09
00
1
CONSTANT_Dynamic_info
例:
// 将 2 重新读到操作数栈顶,准备返回
12: istore_3 // 将 2 取出,存储到第 4 个变量槽 {} {this, 2, e, 2}
04
?表 2.2.4-1 类索引、父类索引、接口索引集合?
1
07
u2
类型
1
0D
浮点型字面量
综上,这段字节码对应的源码应是 System.out.println(&34;Hello World&34;);
4 // 将异常 e 取出,存储到第 5 个变量槽 {} {this, ?, ?, ?, e}
00
第 4 项常量是字段的符号引用,说明此字段在 Happy 类中,变量名为 name,java.lang.String 类型。
000001D0:
CONSTANT_NameAndType_info
BA
// 捕获异常,给 catch 中定义的异常 e 赋值
34 = Utf8
1
final int i1 = 3;
ACC_MODULE
09
S
CONSTANT_Class_info
?表 2.2.7-4 LocalVariableTable 属性结构?
23 = NameAndType
?码 2.2.1-1 错误信息?
// java/lang/System.out:Ljava/io/PrintStream;
descriptor: ()V
name_and_type_index
attribute_name_index
枚举类型
?表 2.2.7-3 Exceptions 属性结构?
名称
09
class_index
1C9 ~ 1CA:方法名索引 0x0012,指向第 18 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 “main”。
try {
1DD ~ 1E5:0xB200051206B60007B1;
u4
?表 2.2.7-1 属性表集合?
// 如果 [0,4) 出现 Exception 以外的异常,捕获不了,跳转至 17:
00
ACC_TRANSIENT
4: istore_1
sourcefile_index
07
u2
11
u2
被声明为 deprecated 的方法和字段
line 6: 8
1
([Ljava/lang/String;)V
类、方法表、字段表
1C7 ~ 1C8:方法的访问标志 0x0009,第 1、4 位为 1,此方法是 public、static 修饰的。
0D
}0: aload_0
01
line 2: 4
method_info
访问常量,直接从常量池中获取值(只包括基本数据类型和字符串字面量,如 CONSTANT_Integer_info、CONSTANT_String_info 等字面量类型)。
return
000001E0:
0x0400
接下来是第一个引导方法:
18
00
Happy.java
00
类型
u2
native
final static int I;
u2
1
0A
line_number_table
1
InnerClasses
attribute_length
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以 “_info” 结尾。一系列连续的同一类型、但数量不定的数据称为某类型的集合。
0E
magic
00
0x0035:转十进制为 53,指向第 53 项 CONSTANT_Utf8_info 型常量,bytes 解码为 “BootstrapMethods”。
0E
当描述符用来描述方法的参数列表和返回值时,按照先参数列表,后返回值的顺序;如 void some() 的描述符为 ()V,java.lang.String toString() 的描述符为 ()Ljava/lang/String;,void main(String[] args) 的描述符为 ([Ljava/lang/String;)V。
标志值
u2
// Happy
类型
u2
this version of the Java Runtime only recognizes class file versions up to 52.0意思大概是 class 文件被 JDK16(60.0)编译,使用 JDK8(52.0)运行只能识别 52.0 以下的版本。
u2
03
u2
0x2A
Java 代码编译成的字节码指令
0x4000
扩展:
?表 2.2.7-5 local_variable_table 表结构?
start_pc
标志位 10
0xB2
0x4000
0x003D:转十进制为 61,指向第 61 项 CONSTANT_MethodType_info 型常量,代表一个方法类型,(I)V 一个int 类型的形参、返回值类型为 void 的方法。
14
00
名称
00
aload_0
00
0x000B,指向第 11 项常量,第 11 项常量是 CONSTANT_Utf8_info 类型,它的 bytes 经解码后为 I,所以此字段类型为 int。
ACC_INIERFACE
00
u2
0x8000
class_index
0D
00
u2
00
ACC_ABSTRACT
000001E0:
00
07
3 = String
constant_pool_count
18B ~ 18C:0x000E,此方法的方法名索引,指向第 14 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 “<init>”。
Address
0x0020
?表 2.2.6-9 LineNumberTable 属性结构?
x = 2;
35 = Utf8
30
类型
from
code
u2
模块
6: iload_2 // 将第 3 个变量槽的值复制到操作数栈顶 {1} {this, 3, 1}
u2
local_variable_table
6
01
0F
8.25
08
6E
attribute_count
0D
类型
Address
descriptor: I
使用十六进制编辑器打开 class 文件,提取一部分:
0x0080
含义
bytes
05
// 将 2 重新读到操作数栈顶,准备返回
03
attribute_name_index
61
1
1
V
// Field name:Ljava/lang/String;
u2
}
1: istore_1 // 将栈顶 1 从操作数栈取出,存储到局部变量表的第 2 个变量槽 {} {this, 1}
02
getstatic 指令的参数,代表一个符号引用,指向第 5 项 CONSTANT_Fieldref_info 类型的常量:java/lang/System.out:Ljava/io/PrintStream;,说明获取的是 System 类型的 out 变量
...
// 如果 [0, 4) 出现 Exception 异常,跳转至 8:
u2
09
74
local_variable_info
u2
1
1
03
ACC_PUBLIC
00
// name:Ljava/lang/String;
1
数量
?表 2.2.3-1 标志?
7 = Methodref
数量
1BF ~ 1C0:0x0004,字节码行号 4。
...
由编译器自动生成
名称
19B ~ 19E:0x00000010,字节码长度(code_length)为 16。[2]
常量池中每一项常量都是一个表,它们都有一个共同的特点,表结构起始的第一位是个 u1 类型的标志位(tag),代表着当前常量属于哪种常量类型。
?表 2.2.6-3 方法访问标志?
code_length
36
// java/lang/Object
名称
03
24
含义
描述
1
指向 CONSTANT_Utf8_info 型常量,值为 “BootstrapMethods”
number_of_exceptions
00
07
0x0010
00
0x03
00
1
表示一个模块中开放或导出的包
1C
00
EC ~ F0:0x4861707079,使用 UTF-8 解码,得 Happy;所以此类的全限定名为 Happy,在默认包下。
attributes
类型
CONSTANT_Long_info
00000180:
putfield
09
1D
此索引指向 CONSTANT_Utf8_info 类型的常量,表示类或接口的全限定名
u2
含义
12:13
9
java/lang/System
ACC_PRIVATE
1
// --- throw 异常 ---
B1
// println:(Ljava/lang/String;)V
double、long 这两种 64 位的数据类型,使用两个变量槽存放。[1]
0x0010 的二进制为 0000 0000 0001 0000,若类是 final 类型,则第 5 位应为 1,否则为 0。
0A
整个 class 文件本质上可以视作是一张表,由所示的数据项按严格顺序排列构成。
u1
此属性是可选的,可以使用 -g:none 或 -g:source 选项取消这项信息。
1D9 ~ 1DC:0x00000009,存储字节码指令的字节长度为 9。
27 = NameAndType
不是运行时必须的属性,可以编译时使用 -g:none 或 -g:lines 选项取消这项信息。
08
20
1: getfield
// String java
6A
00
0B
u4
minor_version
00
03
1
若实现了接口,后面每 2 个字节称为一个接口索引,每个接口索引指向某项 CONSTANT_Class_info 类型的常量,对应一个接口的全限定名。
Code 属性结束,此方法结束。
76
00
10
为栈顶数据指向的对象的实例变量赋值(显式初始化)
02
number_of_exceptions
01
08
4
abstract
u2
22 = NameAndType
08
...
标志位 8
00
LineNumberTable:
CONSTANT_InterfaceMethodref_info
u4
0x0080
标志值
attribute_name_index
// java
ACC_ENUM
09
0x2A
对象类型,如 Ljava/lang/Object;
173 ~ 174:接口数量(interfaces_count),0x0000,转十进制为 0,没有实现任何接口。
数量
36 = Utf8
// {操作数栈最大深度 1} {局部变量表同时生存的局部变量所占最大的槽数 5} {参数个数 1}
6C
attribute_info
attributes
34
1
ACC_NATIVE
197 ~ 198:0x0002,操作数栈(Operand Stack)深度的最大值(max_stack)。JVM 运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度。
?表 2.2.2-6 CONSTANT_Utf8_info 型常量的结构?
byte
u1
1CD ~ 1CE:0x0001,方法的属性数量为 1。
1D1 ~ 1D4:0x00000025,属性值长度为 37:1D5 ~ 1F9。
紧接着是 Code 属性的属性;
02
2A
getstatic
06
14:第 3 项常量池的标志位 0x08,属于 CONSTANT_String_info 类型,代表字符串类型的字面量。
0x0000000C:转十进制为 12,属性剩余长度为 12 个字节。
11: iload_1 // 将第 2 个变量槽的值复制到操作数栈顶 {2} {this, 2, e}
attributes_count
0xB6
?表 2.2.5-3 字段访问标志?
private
1: istore_1
数量
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
数量
methods_count
// Field i1:I
descriptor_index
0A
08
0C
0B
06
[1] 空出的一项为 0,将索引值设为 0,代表不引用任何一个常量池项目。
00
0B
ACC_PRIVATE
10:11
Exceptions
0x003D:转十进制为 61...
0x0008
00000000:
B2
ACC_STATIC
00
19 = Utf8
local_variable_table_length
07
20: istore_1 // 将 3 取出,存储到第 2 个变量槽 {} {this, 3, ?, ?, e}
access_flags
attribute_count
助记符
0C
1
attribute_length
16: ireturn // 方法结束,返回 2
12
类型
Exceptions 属性
name_index
// &34;<init>&34;:()V
0xB5
B6
18D ~ 18E:0x000F,此方法的描述符索引,指向第 15 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 ()V。
0F
此索引指向 CONSTANT_NameAndType_info 类型的常量,表示字段名及描述符
0E
10
0
1AF ~ 1B0:异常表长度(exception_table_length),0x0000,即方法抛出的异常个数为 0。
D
67
java.lang.String name;
final class Happy
void
transient
// x = 3;
1
0x0004
?表 2.2.2-3 CONSTANT_Methodref_info 常量的结构?
6C
74
17 any
attribute_length
00
00
数量
Deprecated
0B
(Ljava/lang/String;)V第 1 项常量是方法的符号引用,代表调用 Object 的 <init> 方法;<init> 是构造方法,内部调用父类构造器(Object 除外)、显示初始化实例变量和执行实例语句块(如果有)、执行本类的构造器中的语句。
名称
4
12: putfield
bootstrap_method_ref
0x0000 的每位都是 0,此字段没有修饰符。
u2
?表 2.2.6-7 指令含义?

u2
stack=2, locals=1, args_size=1
ACC_FINAL
8
0x0020
72
max_locals
0x1000
000001B0:
1
0x2A
07
37:38
字符串类型字面量
attributes
181 ~ 182:0x000C,此字段的字段名索引,指向第 12 项 CONSTANT_Utf8_info 类型的常量,此常量的 bytes 经解码后为 “name”,字段名为 name。
FE
如果当字节码从第 i 行抛出了类型为 catch_type 或其子类的异常,i \in [start_pc,end_pc) ,则转到第 handler_pc 行继续处理;当 catch_type 为 0 时,表示任何异常情况都需要转到 handler_pc 处处理。
37 = Utf8
u4
15: iload_3 // 将第 4 个变量槽的值复制到操作数栈顶 {2} {this, 3, e, 2}
171 ~ 172:0x0009 这两个字节称为父类索引(super_class),用于确定父类的全限定名,除了 java.lang.Object 外,所有类都有父类,索引都不为 0。[1]
08
exception_index_table 中的每个成员都是对常量池的有效索引,指向 CONSTANT_Class_info 型常量,表示异常的类型。
tag
15
major version: 52
1
[1] 引入泛型后,字段的描述符中泛型的参数化类型被擦除,不能准确描述泛型类型。如 List<String> list 的描述符 descriptor 为 “Ljava/util/List”;
00
0x0800
ACC_STATIC
Happy
61
0x0001
00
u1
0x0030 的二进制为 0000 0000 0011 0000,第 5、6 位为 1,代表它是使用 invokespecial 新语义的 final 类,其它位为 0,说明此类型没有 public、abstract 修饰...
00
00
03
?码 2.2.6-2 方法表集合?
final static Integer I = 3;隐式调用 Integer.valueOf(3)方法,不会生成 ConstantValue 属性,在 <clinit> 方法中赋值。
类文件
第 3 项常量是字符串字面量 “java”,可表示为 java.lang.String 的实例。
将 int、float 或 String 类型的常量值从常量池中推送至栈顶
3: istore_2 // 将 1 取出,存储到第 3 个变量槽中 {} {this, 1, 1}
此索引 CONSTANT_NameAndType_info 类型的常量,表示方法名称及描述符
1E2 ~ 1E3:0x0015,源文件名索引(sourcefile_index),指向第 21 项 CONSTANT_Utf8_info 型常量:“Happy.java”;所以源文件名应为 “Happy”。
2A
强行运行会报错,抛出 UnsupportedClassVersionError:
u2
CONSTANT_Double_info
接下来是方法表(method_info)结构,用于描述方法。
数量
当虚拟机加载类型时,将会从常量池获得对应的符号引用,再在类创建时或运行时,解析、翻译到具体的内存地址之中。[2]
0xB1
1
0x0003:引导方法参数数量为 3。
03
u2
名称
u1
00000170:
// x = 1;对应两个指令
01
strictfp
标志值
// java/io/PrintStream.println:(Ljava/lang/String;)V
09
1
aload_0
名称
1
引导方法参数数量
类型
0C
类型
5: return?码 2.2.7-5 常量赋值?
特征签名(Signature)多了一项参数化类型的信息: “Ljava/util/List<Ljava/lang/String;>;”。
描述
?表 2.2.1-1 魔数与版本号?
flags:?表 2.2.6-1 方法表集合?
u2
09
06
// x = 3;
10
ldc 指令的参数,代表某字面量,指向第 6 项 CONSTANT_String_info 类型的常量:“Hello World”
影响:所有参数名称都会丢失,IDE 会使用 arg0、arg1 之类的占位符代替原有参数名,在调试期间无法根据参数名称从上下文中获取参数值。
08
这是 invokespecial 指令的参数,代表一个符号引用,指向第 1 项 CONSTANT_Methodref_info 类型的常量:java/lang/Object.&34;\<init>&34;:()V,说明应调用 Object 的无参构造
04
06
// java/lang/Object.&34;<init>&34;:()V
06
u2
attribute_name_index
1
0F
06
1
193 ~ 196:属性值占用的长度(attribute_length),0x00000030,转十进制为 48;从 197 ~ 1C6 都是 Code 属性的内容,这个属性表的长度为 48 + 6 = 54 个字节。
28
助记符
Java虚拟机的做法是将局部变量表中的变量槽进行重用,当代码执行超出一个局部变量的作用域时,这个局部变量所占的变量槽可以被其他局部变量所复用,javac 编译器会根据变量的作用域来分配变量槽给各个变量使用,根据同时生存的最大局部变量数量和类型计算出 max_locals 的大小。
名称
0A
02
获取指定类的类变量,并将其值压入操作数栈顶
CONSTANT_InvokeDynamic_info
04
LineNumberTable
u2
Address
flags: ACC_PUBLIC
1
?码 2.2.6-1 异常处理源码及反编译后的指令?
final
0E
17
9 = Class
0B
09
08
char
无符号数属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1、2、4、8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成字符串值。
06
9.22
0: aload_0
名称
invokevirtual 指令的参数,指向第 7 项 CONSTANT_Methodref_info 类型的常量:java/io/PrintStream.println:(Ljava/lang/String;)V,代表调用 PrintStream 类的 println(String)方法。
invokevirtual
00000170:
177 ~ 178:字段访问标志(access_flags),识别字段的修饰符,0x0000。
12 = Utf8
155:第 38 项常量的标志位 0x01,属于 CONSTANT_Utf8_info 类型,代表 UTF-8 编码的字符串,用来表示字符串字面量、类型的全限定名、字段名、...[3]
01
u2
内容导视:
04
00
02
2 = Fieldref
0D
attribute_info
以下为例:
1
transient
interfaces_count
0x0001
?表 2.2.6-8 异常表结构?
1
?表 2.2.3-2 访问标志?
private
name_index:局部变量名索引
双精度浮点型字面量
0x0002
short
static
000001F0:
// 如果 [8, 13) 出现任意异常,转到 17:
constant_pool
名称
01
00
含义
1
8 Class java/lang/Exception
接下来是第 2 个字段;
?表 2.2.6-5 不同属性的含义?
仅当一个类为局部内部类或匿名类时才能拥有此属性,用于标识这个类所在的外围方法
()V
x = 3;
ACC_BRIDGE
L
11
x = 1;
2
1
07
B1
to target type
10 = Utf8
31
// i:I
01
接口类型
使用 javap 反编译工具,加 -verbose 参数输出 class 文件的部分内容:
16D ~ 16E:0x0030,访问标志(access_flags),表示类型的修饰符。
数量
3
不是运行必需的属性,可以编译时使用参数 -g:none 或 -g:vars 取消此项信息。
attribute_count
19: iconst_3 // 将常量 3 复制到操作数栈顶 {3} {this, ?, ?, ?, e}
00
对 class 文件的解析到此结束,下面是从 《深入理解 Java 虚拟机》摘抄的一部分属性表。
start_pc
F
long
06 ~ 07:主版本号(Major Version)0x0034,对应的十进制为 52,代表 JDK8;JDK1 从 45 开始,每隔一个大版本加 1;高版本的 JDK 可以向下兼容以前版本的 class 文件,反之则不行。
[2] class 文件里,所有的 “.” 都被斜杆 “/” 代替,例 “java.lang.Object” 变为 “java/lang/Object”。
这是 putfield 指令的参数,代表一个符号引用,指向第 2 项 CONSTANT_Fieldref_info 型常量:Happy.i:I,应为 Happy 类的 int 类型的变量名为 i 的变量赋值
1
0F
20 = Utf8
00
index:局部变量在栈帧的局部变量表中变量槽的位置。当变量数据类型大于 32 位时,它占用的变量槽为 index、index + 1 这两个。
5: istore_1 // 将 3 取出,存储到第 2 个变量槽 {} {this, 3, 1}
结束位置
u2
00
u4
字节码
07
ACC_TRANSIENT
// Field java/lang/System.out:Ljava/io/PrintStream;
}此字段表不会有 ConstantValue 属性,在 <clinit> 方法中赋值。
1F0 ~ 1F1:0x0002,行号表长度为 2。
0x06
public
0x1000
B5
?表 2.2.5-1 字段表集合?
1 个字节最多表示 256 条指令。目前,《Java虚拟机规范》已经定义了其中约 200 条编码值对应的指令含义,编码与指令之间的对应关系可查阅《深入理解 Java 虚拟机》的附录 C:“虚拟机字节码指令表”。
0x0036:转十进制为 54,指向第 54 项 CONSTANT_MethodHandle_info 型常量,代表一个方法句柄,REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(...)... 代表调用某静态方法。
00
0x0007
7
199 ~ 19A:0x0001,局部变量表所需的存储空间(max_locals)。max_locals 的单位是变量槽(Slot),变量槽是 JVM 为局部变量分配内存所使用的最小单位。
ACC_VOLATILE
9: aload_0
00000160:
ACC_ANNOTATION
length:作用范围覆盖的长度
volatile
00
0E
00
LineNumberTable:
操作数栈和局部变量表直接决定一个该方法的栈帧所耗费的内存,不必要的操作数栈深度和变量槽数量会造成内存的浪费。
0x0040
attributes_count
boolean
attribute_name_index
- 0000
- 0000
- 0000
- 0001
- 0000