字节码结构
ClassFile {
u4 magic;
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;
interfaces_info interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
java 源码
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
使用javac将Java源码编译为字节码,如下所示。
class 字节码
cafe babe 0000 0034 0022 0a00 0600 1409 0015 0016 0800 170a 0018 0019 0700 1a07 001b 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 124c 6f63 616c 5661 7269 6162 6c65 5461 626c 6501 0004 7468 6973 0100 0c4c 4865 6c6c 6f57 6f72 6c64 3b01 0004 6d61 696e 0100 1628 5b4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5601 0004 6172 6773 0100 135b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 0100 0a53 6f75 7263 6546 696c 6501 000f 4865 6c6c 6f57 6f72 6c64 2e6a 6176 610c 0007 0008 0700 1c0c 001d 001e 0100 0d48 656c 6c6f 2c20 576f 726c 6421 0700 1f0c 0020 0021 0100 0a48 656c 6c6f 576f 726c 6401 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 106a 6176 612f 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 7401 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d01 0007 7072 696e 746c 6e01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5600 2100 0500 0600 0000 0000 0200 0100 0700 0800 0100 0900 0000 2f00 0100 0100 0000 052a b700 01b1 0000 0002 000a 0000 0006 0001 0000 0001 000b 0000 000c 0001 0000 0005 000c 000d 0000 0009 000e 000f 0001 0009 0000 0037 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0200 0a00 0000 0a00 0200 0000 0300 0800 0400 0b00 0000 0c00 0100 0000 0900 1000 1100 0000 0100 1200 0000 0200 13
u4 magic
CA FE BA BE,class 文件类型的标志
Q:例如png,jpg等文件的文件名后缀已经标志了文件的类型,我们又要使用magic number来标志文件类型呢?
A:因为png,jpg等文件名后缀比较容易修改。
u2 minor_version & u2 major_version
00 00 00 34 -> 00 00 00 52
| Java 版本 | Major Version |
|---|---|
| Java 8 | 52 |
Java 的版本号实际上经历了几次变化:
- Java 1.0 到 Java 1.4:在这些早期版本中,主版本号前都有“1.”前缀。
- Java 5:虽然内部版本号是 1.5,但这是第一次官方主要使用不带前缀的版本号(即 Java 5 而不是 Java 1.5)。
- Java 6 和 Java 7:这些版本也遵循了相同的命名模式,分别为 Java 6(内部版本号 1.6)和 Java 7(内部版本号 1.7)。
到了 Java 8,虽然官方称其为 Java 8,很多开发者和文档仍然习惯于使用带有“1.”的传统命名方式,即 Java 1.8。这种用法在社区中广泛存在,部分是因为长期习惯,部分是为了在谈论版本时保持一致性。
从 Java 9 开始,Oracle 放弃了这种带“1.”的命名方式,转而使用单一的主版本号(如 Java 9, Java 10 等),这样做的目的是简化版本信息,使其更直观。
u2 constant_pool_count
常量池大小会比真实的常量数量大1。
00 22 -> 34
so 真实的常量个数为33.
constant_pool[]
| Constant Type | Value |
|---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
#1 0A 00 06 00 14
- CONSTANT_Methodref
- cp_info #6
- cp_info #20
#2 09 00 15 00 16
- CONSTANT_Fieldref
- cp_info #21
- cp_info #22
#3 08 00 17
- CONSTANT_String
- cp_info #23
#4 0A 00 18 00 19
- CONSTANT_Methodref
- cp_info #24
- cp_info #25
#5 07 00 1A
- CONSTANT_Class
- cp_info #26
#6 07 00 1B
- CONSTANT_Class
- cp_info #27
#7 01 00 06 3C 69 6E 69 74 3E
- CONSTANT_Utf8
- length 6
- <init>
#8 01 00 03 28 29 56
- CONSTANT_Utf8
- length 3
- ()V
#9 01 00 04 43 6F 64 65
- CONSTANT_Utf8
- length 4
- Code
#10 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
- CONSTANT_Utf8
- length 15
- LineNumberTable
#11 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65
- CONSTANT_Utf8
- length 18
- LocalVariableTable
#12 01 00 04 74 68 69 73
- CONSTANT_Utf8
- length 4
- this
#13 01 00 0C 4C 48 65 6C 6C 6F 57 6F 72 6C 64 3B
- CONSTANT_Utf8
- length 12
- LHelloWorld;
#14 01 00 04 6D 61 69 6E
- CONSTANT_Utf8
- length 4
- main
#15 01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
- CONSTANT_Utf8
- length 22
- ([Ljava/lang/String;)V
#16 01 00 04 61 72 67 73
- CONSTANT_Utf8
- length 4
- args
#17 01 00 13 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B
- CONSTANT_Utf8
- length 19
- [Ljava/lang/String;
#18 01 00 0A 53 6F 75 72 63 65 46 69 6C 65
- CONSTANT_Utf8
- length 10
- SourceFile
#19 01 00 0F 48 65 6C 6C 6F 57 6F 72 6C 64 2E 6A 61 76 61
- CONSTANT_Utf8
- length 15
- HelloWorld.java
#20 0C 00 07 00 08
- CONSTANT_NameAndType
- cp_info #7
- cp_info #8
#21 07 00 1C
- CONSTANT_Class
- cp_info #28
#22 0C 00 1D 00 1E
- CONSTANT_NameAndType
- cp_info #29
- cp_info #30
#23 01 00 0D 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21
- CONSTANT_Utf8
- length 13
- Hello, World!
#24 07 00 1F
- CONSTANT_Class
- cp_info #31
#25 0C 00 20 00 21
- CONSTANT_NameAndType
- cp_info #32
- cp_info #33
#26 01 00 0A 48 65 6C 6C 6F 57 6F 72 6C 64
- CONSTANT_Utf8
- length 10
- HelloWorld
#27 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
- CONSTANT_Utf8
- length 16
- java/lang/Object
#28 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D
- CONSTANT_Utf8
- length 16
- java/lang/System
#29 01 00 03 6F 75 74
- CONSTANT_Utf8
- length 3
- out
#30 01 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B
- CONSTANT_Utf8
- length 21
- Ljava/io/PrintStream;
#31 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D
- CONSTANT_Utf8
- length 19
- java/io/PrintStream
#32 01 00 07 70 72 69 6E 74 6C 6E
- CONSTANT_Utf8
- length 7
- println
#33 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
- CONSTANT_Utf8
- length 21
- (Ljava/lang/String;)V
u2 access_flags
两个字节16位表示access_flags。
00 21 -> ACC_PUBLIC + ACC_SUPER (QAQ)
| 3 | 2 | 1 | 0 |
|---|---|---|---|
| 15 14(ACC_ENUM) 13(ACC_ANNOTATION) 12(ACC_SYNTHETIC) | 11 10(ACC_ABSTRACT) 9(ACC_INTERFACE) 8 | 7 6 5(ACC_SUPER) 4(ACC_PUBLIC) | 3 2 1 0(ACC_PUBLIC) |
| Flag Name | Value | Interpretation |
|---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_FINAL | 0x0010 | Declared final; no subclasses allowed. |
ACC_SUPER | 0x0020 | Treat superclass methods specially when invoked by the invokespecial instruction. |
ACC_INTERFACE | 0x0200 | Is an interface, not a class. |
ACC_ABSTRACT | 0x0400 | Declared abstract; must not be instantiated. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
ACC_ANNOTATION | 0x2000 | Declared as an annotation type. |
ACC_ENUM | 0x4000 | Declared as an enum type. |
u2 this class & u2 super_class && u2 interfaces_count && interfaces[interfaces_count]
00 05 00 06 00 00 对应的链接
this class 和 super class 都是指向常量池的索引,均使用两个字节表示。
u2 this_class;
u2 super_class;
u2 interfaces_count;
interface_info interfaces[interfaces_count];
00 05 指向常量池中的一个 CONSTANT_Class_info 结构。CONSTANT_Class_info 结构中包含一个指向常量池中 CONSTANT_Utf8_info 结构的索引,CONSTANT_Utf8_info 结构中存储了当前类的全限定名(包括包名)指向HelloWorld。
00 06 指向常量池中的一个 CONSTANT_Class_info 结构。CONSTANT_Class_info 结构中包含一个指向常量池中 CONSTANT_Utf8_info 结构的索引,CONSTANT_Utf8_info 结构中存储了父类的全限定名(包括包名),指向java/lang/Object。 如果当前类是 java.lang.Object,则 super_class 的值为 0。
00 00 interfaces_count: 该项的值指定了该类或接口直接实现或继承的直接超接口(superinterfaces)数量。当前文件显示interfaces_count值为0,所以后续的interface[interfaces_count]不占用字节。
直接超接口(direct superinterfaces) :指的是一个类或接口直接声明实现(对于接口)或继承(对于类)的那些接口。
interface[interfaces_count]: 接口数组中的每个值都必须是 constant_pool 表中的有效索引。
u2 fields_count & fields[fields_count]
两个字节00 00表示fields_count。
u2 fields_count
fields_info fields[fields_count]
fields_count 00 00
fields[fields_count]
field_count: 记录此类或接口类型声明的所有字段(包括类变量和实例变量)的数量。当前文件显示fields_count为0,所以后续的fields[fields_count]不占用字节。
字段描述符映射表
| 描述符 | 类型 |
|---|---|
| B | byte 类型 |
| C | char 类型 |
| D | double 类型 |
| F | float 类型 |
| I | int 类型 |
| J | long 类型 |
| S | short 类型 |
| Z | boolean 类型 |
| L ClassName; | 引用类型,"L" + 对象类型的全限定名 + ";" |
| [ | 一维数组 |
u2 methods_count & methods[methods_count]
两个字节表示methods_count。
u2 methods_count
method_info methods[methods_count]
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
methods_count
u2 methods_count
method_info methods[methods_count]
00 02 共有两个method。method_info_1,method_info_2。
method_info 1
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
注意这里的attributes是具体的某个属性。比如下面的code属性。
00 01 public,参考下文修饰符表;
00 07 常量池(constant pool)中的索引:<init>;
00 08 常量池(constant pool)中的索引:()V;
00 01 属性计数(attributes_count),表示该方法有1个属性;
attribute_info code of method_info 1
Code_attribute {
u2 attribute_name_index; // 对应常量池中的名称
u4 attribute_length; // 描述attribute的长度
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
00 09 这是一个属性的索引,从常量池中引用,指向Code属性;
00 00 00 2F Code属性的长度,即随后的47个字节表示Code属性;
00 01 max_stacks,即操作栈的最大深度为1;
00 01 max_locals,本地变量表的大小为1;
00 00 00 05 code_length,代码的长度(字节),表示随后的5个字节用来表示代码;
2A B7 00 01 B1 具体的字节码指令,参考文章;
2A B7 00 01 B1可以解释如下:
aload_0: 将第一个局部变量加载到栈顶。invokespecial 0001: 调用常量池索引为1的方法,通常是构造方法或私有方法。return: 从当前方法返回。
00 00 exception_table_length,异常表的长度;
00 02 属性计数(attributes_count),表示随后有两个属性;
attribute_info LineNumberTable of attribute_info code of method_info 1
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
00 0A 属性名称索引,指向常量池中表示属性名的条目,通常这个值指向"LineNumberTable"属性;
00 00 00 06 属性长度,6个字节;
00 01 line_number_table_length,表示接下来有1个行号映射项;
00 00 00 01 行号映射。第一个值0000是start_pc,表示从第0个字节码偏移量开始,第二个值0001是行号,表示这个偏移量对应源代码中的行号;
attribute_info LocalVariableTable of attribute_info code of method_info 1
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}
00 0B 属性名称索引,指向常量池中表示另一个属性名的条目,是"LocalVariableTable";
00 00 00 0C 属性长度,12个字节;
00 01 local_variable_table_length,表示接下来有1个局部变量表项;
00 00 start_pc,表示局部变量的生命周期开始于哪个字节码偏移;
00 05 length,表示局部变量的生命周期持续的字节码长度;
00 0C name_index,局部变量名称的常量池索引,指向this;
00 0D descriptor_index,局部变量描述符的常量池索引(如类型),指向LHelloWorld;
00 00 index,局部变量在局部变量表中的索引。
this对应的索引是0,由此可以看出this被编译器将Java文件编译为字节码的时候会将this作为第一个参数。
method_info 2
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
00 09:00 09是 access_flags 的十六进制表示,转换成二进制是 0000 0000 0000 1001。可以参考下文修饰符表。
- ACC_PUBLIC (0x0001) : 表示该方法是
public的,可以被任何类访问。 - ACC_STATIC (0x0008) : 表示该方法是
static的,属于类本身,而不是类的实例
00 0e 代表name_index,对应常量池索引 main。
00 0f 代表descriptor_index,对应常量池索引 ([Ljava/lang/String;)V
00 01 代表attributes_count,表示该方法有1个属性;
attribute_info code of method_info 2
Code_attribute {
u2 attribute_name_index; // 对应常量池中的名称
u4 attribute_length; // 描述attribute的长度
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
00 09 这是一个属性的索引,从常量池中引用,指向Code属性;
00 00 00 37 Code属性的长度,即随后的55个字节表示Code属性;
00 02 max_stacks,即操作栈的最大深度为2;
00 01 max_locals,本地变量表的大小为1;
00 00 00 09 code_length,代码的长度(字节),表示随后的9个字节用来表示代码;
b2 00 02 12 03 b6 00 04 b1 具体的字节码指令,参考文章;
b2: 这是getstatic指令的操作码,用于获取类的静态字段的值。00 02: 这是getstatic指令的操作数,是一个 16 位的常量池索引,指向常量池中的第 2 个常量。 这个常量应该是一个字段符号引用,包含了要获取的静态字段的信息(字段所属的类、字段名、字段类型)。12: 这是ldc指令的操作码,用于将一个常量值加载到操作数栈顶。03: 这是ldc指令的操作数,是一个 8 位的常量池索引,指向常量池中的第 3 个常量。 这个常量可以是 int、float、String、Class 类型的值。b6: 这是invokevirtual指令的操作码,用于调用对象的实例方法。00 04: 这是invokevirtual指令的操作数,是一个 16 位的常量池索引,指向常量池中的第 4 个常量。 这个常量应该是一个方法符号引用,包含了要调用的实例方法的信息(方法所属的类、方法名、方法参数类型、方法返回类型)。b1: 这是return指令的操作码,用于结束方法的执行并返回。
00 00 exception_table_length,异常表的长度;
00 02 属性计数(attributes_count),表示随后有两个属性;
attribute_info LineNumberTable of attribute_info code of method_info 2
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{
u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
00 0A 属性名称索引,指向常量池中表示属性名的条目,通常这个值指向"LineNumberTable"属性;
00 00 00 0A 属性长度,10个字节;
00 02 line_number_table_length,表示接下来有2个行号映射项;
00 00 00 03 行号映射。第一个值0000是start_pc,表示从第0个字节码偏移量开始,第二个值0003是行号,表示这个偏移量对应源代码中的行号;
00 08 00 04 行号映射。第一个值0008是start_pc,表示从第8个字节码偏移量开始,这里对应return,第二个值0004是行号,表示这个偏移量对应源代码中的行号;
attribute_info LineNumberTable of attribute_info code of method_info 2
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{
u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}
00 0B 属性名称索引,指向常量池中表示另一个属性名的条目,是"LocalVariableTable";
00 00 00 0C 属性长度,12个字节;
00 01 local_variable_table_length,表示接下来有1个局部变量表项;
00 00 start_pc,表示局部变量的生命周期开始于哪个字节码偏移;
00 09 length,表示局部变量的生命周期持续的字节码长度为9,刚好对应这段字节码指令的长度。;
00 10 name_index,局部变量名称的常量池索引,指向args;
00 11 descriptor_index,局部变量描述符的常量池索引(如类型),指向[Ljava/lang/String;;
00 00 index,局部变量在局部变量表中的索引。
u2 attributes_count & attribute_info attributes[attributes_count]
attributes_count
00 01 表示只有一个attribute。
attribute_info
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
00 12 属性名称索引,指向常量池中SourceFile。
00 00 00 02 SourceFile 属性的长度(字节数),固定为 2。
00 13 指向常量池中的一个字符串常量HelloWorld.java,该字符串常量是源文件的名称。。
注释
字节码偏移量
- 类似于数组索引,用于定位字节码指令在指令序列中的位置。
- 从 0 开始计数,第一个字节的偏移量是 0,第二个字节的偏移量是 1,以此类推。
示例
假设一个方法的字节码指令序列如下:
0: iconst_1
1: istore_1
2: iload_1
3: iadd
4: istore_2
5: return
那么,偏移量为 5 的指令就是 return 指令。
LocalVariableTable
-
local_variable_table: 一个数组,每个元素包含一个局部变量的描述信息。 每个元素包含以下字段:
- start_pc: 该局部变量作用域的起始字节码偏移量。
- length: 该局部变量作用域的长度(以字节码指令为单位)。
- name_index: 指向常量池中的一个字符串常量,该字符串常量是局部变量的名称。
- descriptor_index: 指向常量池中的一个字符串常量,该字符串常量是局部变量的类型描述符。
- index: 该局部变量在局部变量表中的索引。
举例说明
假设 LocalVariableTable 属性包含以下条目:
start_pc length name_index descriptor_index index
0 10 1 2 0
5 5 3 4 1
同时假设常量池中包含以下信息:
#1: "a"
#2: "I"
#3: "b"
#4: "Ljava/lang/String;"
那么,我们可以得出以下结论:
- 方法中有一个名为 "a" 的 int 类型的局部变量,它的作用域从字节码偏移量 0 开始,长度为 10 个字节码指令。 它在局部变量表中的索引是 0。
- 方法中有一个名为 "b" 的 String 类型的局部变量,它的作用域从字节码偏移量 5 开始,长度为 5 个字节码指令。 它在局部变量表中的索引是 1。
修饰符表
| Flag Name | Value | Interpretation |
|---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; accessible only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; must not be overridden (§5.4.5). |
ACC_SYNCHRONIZED | 0x0020 | Declared synchronized; invocation is wrapped by a monitor use. |
ACC_BRIDGE | 0x0040 | A bridge method, generated by the compiler. |
ACC_VARARGS | 0x0080 | Declared with variable number of arguments. |
ACC_NATIVE | 0x0100 | Declared native; implemented in a language other than Java. |
ACC_ABSTRACT | 0x0400 | Declared abstract; no implementation is provided. |
ACC_STRICT | 0x0800 | Declared strictfp; floating-point mode is FP-strict. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |