反汇编,以下命令可以查看相对可读的详细结构
javap -verbose ByteCode.class
与Class二进制文件并不是直接对齐的
Class二进制文件结构参照表
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;实现的接口数u2 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];附加属性表
}
常量池入口,占用二个字节,常量池中的第0个位置被我们的jvm占用了表示为null 所以我们通过编译出来的常量池索引是从1开始的。
常量池是 class 文件中一个用于存放各种**字面量(如字符串、数值)和符号引用(类名、字段、方法名等)**的表。
常量池常量结构表
u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数
tag均是u1
tag | 常量类型 | 名称 | 结构说明 | 示例 |
---|---|---|---|---|
1 | CONSTANT_Utf8 | UTF-8 编码字符串 | u2 length + u1[length] bytes :MUTF-8 编码的字符串 | “Hello”, “main”, “(I)V” |
3 | CONSTANT_Integer | 整型常量 | u4 bytes :32 位整型值 | 100 、-1 |
4 | CONSTANT_Float | 浮点型常量 | u4 bytes :IEEE 754 float | 3.14f |
5 | CONSTANT_Long | 长整型常量 | u4 high_bytes + u4 low_bytes (注意占用两项) | 123456789012345L |
6 | CONSTANT_Double | 双精度浮点常量 | u4 high_bytes + u4 low_bytes (注意占用两项) | 3.1415926535 |
7 | CONSTANT_Class | 类或接口名 | u2 name_index :指向一个 Utf8 常量 | java/lang/String |
8 | CONSTANT_String | 字符串字面量引用 | u2 string_index :指向一个 Utf8 常量 | "Hello" |
9 | CONSTANT_Fieldref | 字段引用 | u2 class_index + u2 name_and_type_index | System.out |
10 | CONSTANT_Methodref | 方法引用 | u2 class_index + u2 name_and_type_index | println(String) |
11 | CONSTANT_InterfaceMethodref | 接口方法引用 | u2 class_index + u2 name_and_type_index | Comparable.compareTo(Object) |
12 | CONSTANT_NameAndType | 名字和类型描述符组合,可以是字段名+类型,也可以是方法名+方法描述符 | u2 name_index + u2 descriptor_index | <init>:()V , add:(II)I |
13456属于字面量、其他属于符号引用类型
我们的常量池可以看作我们的java class类的一个资源仓库(比如Java类定的方法和变量信息),我们后面的方法类的信息的描述信息都是通过索引去常量池中获取。
- 常量池中主要存放二种常量,一种是字面量 一种是符号引用
在JVM规范中,每个字段或者变量都有描述信息,描述信息的主要作用是数据类型,方法参数列表,返回值类型等。 基本参数类型和void类型都是用一个大写的字符来表示,对象类型是通过一个大写L加全类名表示,这么做的好处就是在保证jvm能读懂class文件的情况下尽量的压缩class文件体积.
- 基本数据类型表示:
- B---->byte
- C---->char
- D---->double
- F----->float
- I------>int
- J------>long
- S------>short
- Z------>boolean
- V------->void
- 对象类型:
- String------>Ljava/lang/String;(前面有个L后面有一个分号)
- 对于数组类型: 每一个唯独都是用一个前置[来表示比如:
- int[] ------>[I
- String [][]------>[[Ljava.lang.String;
用描述符来描述方法的,先参数列表,后返回值的格式,参数列表按照严格的顺序放在()中
比如String getUserInfoByIdAndName(int id,String name) 的方法描述符号
- (I,Ljava/lang/String;)Ljava/lang/String;
Class文件结构类的访问标识符号解析 Access_flag
标志位(hex) | 标志名 | 含义 |
---|---|---|
0x0001 | ACC_PUBLIC | 类是 public |
0x0010 | ACC_FINAL | 类是 final (不可继承) |
0x0020 | ACC_SUPER | 使用了新的 invokespecial 语义(Java 1.0.2+) |
0x0200 | ACC_INTERFACE | 是接口 |
0x0400 | ACC_ABSTRACT | 是抽象类 |
0x1000 | ACC_SYNTHETIC | 编译器生成的类 |
0x2000 | ACC_ANNOTATION | 是注解(Annotation) |
0x4000 | ACC_ENUM | 是枚举类 |
jvm规范并没有穷举出所以的类型 而是通过位运算的出来的。
0x0021 = 0x0020 位运算 0x0001 那么我们可以得出这个class的访问权限是ACC_PUBLIC 和ACC_SUPER
This class name的描述当前的所属类
this class name 占用二个字节,表示索引
super class name (当前class的父类名字)
同样占用二个字节,也是表示索引值
接口信息
实现的接口数量
占用二个字节表示实现了几个接口,是实现接口数量的上限也是0xffff
实现的接口
每个接口名占两个字节,来表示接口的位于常量池中的索引
字段表信息分析
字段结构
field_info {u2 access_flags; // 访问标志(如 public、static、final 等)u2 name_index; // 字段名称(指向常量池的 Utf8 项)u2 descriptor_index; // 字段类型描述符(指向常量池的 Utf8 项)u2 attributes_count; // 属性数量attribute_info attributes[attributes_count]; // 属性表(如 ConstantValue 等)
}
field_info.attributes一般会存:
- ConstantValue
只有static final
常量字段才有,存储该字段的常量值(比如public static final int MAX = 100;
),内容是一个指向常量池中具体常量的索引。 - Deprecated
标记字段已经废弃,提示编译器或工具警告。 - Synthetic
标记字段是编译器生成的,不是源代码写的。 - RuntimeVisibleAnnotations 和 RuntimeInvisibleAnnotations
存储字段上的注解信息。
字段访问标志
标志位(hex) | 标志名 | 含义 |
---|---|---|
0x0001 | ACC_PUBLIC | 字段是 public |
0x0002 | ACC_PRIVATE | 字段是 private |
0x0004 | ACC_PROTECTED | 字段是 protected |
0x0008 | ACC_STATIC | 字段是 static |
0x0010 | ACC_FINAL | 字段是 final |
0x0040 | ACC_VOLATILE | 字段是 volatile |
0x0080 | ACC_TRANSIENT | 字段是 transient |
0x1000 | ACC_SYNTHETIC | 编译器生成的字段 |
0x4000 | ACC_ENUM | 枚举类型字段 |
方法表信息分析
方法结构
method_info {u2 access_flags; // 访问标志(public、static、final等)u2 name_index; // 方法名索引(指向常量池)u2 descriptor_index; // 方法描述符索引(参数类型和返回值)(()V 表示的是无参无返)u2 attributes_count; // 属性个数attribute_info attributes[attributes_count]; // 属性数组(如Code属性)
}
method_info.attributes一般会存:
属性名 | 说明 |
---|---|
Code | 最核心: 存放方法的字节码指令、栈帧信息等(非 abstract/native 方法必须有) |
Exceptions | 声明该方法可能抛出的异常列表(即 throws ) |
LineNumberTable | 源代码行号到字节码地址的映射(调试用) |
LocalVariableTable | 局部变量名、类型、作用域(调试用) |
RuntimeVisibleAnnotations | 运行时可见注解 |
RuntimeInvisibleAnnotations | 编译期注解,运行时不可见 |
Synthetic | 标记该方法是编译器生成的,不是源代码写的 |
Deprecated | 标记该方法已过时 |
Signature | 泛型信息描述(如果用了泛型) |
MethodParameters | 记录参数名(从Java 8开始) |
方法访问标志
标志位(hex) | 标志名 | 含义 |
---|---|---|
0x0001 | ACC_PUBLIC | 方法是 public |
0x0002 | ACC_PRIVATE | 方法是 private |
0x0004 | ACC_PROTECTED | 方法是 protected |
0x0008 | ACC_STATIC | 方法是 static |
0x0010 | ACC_FINAL | 方法是 final |
0x0020 | ACC_SYNCHRONIZED | 方法是 synchronized |
0x0040 | ACC_BRIDGE | 编译器生成的桥接方法 |
0x0080 | ACC_VARARGS | 方法使用可变参数 |
0x0100 | ACC_NATIVE | 方法是本地方法(native) |
0x0400 | ACC_ABSTRACT | 抽象方法 |
0x0800 | ACC_STRICT | 使用严格浮点数计算 |
0x1000 | ACC_SYNTHETIC | 编译器生成的方法 |
attribute_info
attribute_info {u2 attribute_name_index;//attribute名称常量池索引u4 attribute_length;//info长度u1 info[attribute_length];//具体数据
}
- 类级别属性(ClassFile 末尾的 attributes[])
- SourceFile_index
SourceFile_attribute {u2 attribute_name_index; // 常量池中 "SourceFile" 字符串的索引u4 attribute_length; // 属性长度,固定为2u2 sourcefile_index; // 指向常量池中一个 Utf8 字符串,表示源文件名(如 "Test.java")}
- SourceFile_index
- 字段级别属性(field_info 中的 attributes[])
- ConstantValue
用于修饰 静态字段(static final),表示该字段的编译时常量值,比如基本类型的字面量(int、long、float、double、String)。JVM 加载类时,会将这个值赋给对应的静态变量。ConstantValue_attribute {u2 attribute_name_index; // 指向常量池中 "ConstantValue" 的 Utf8u4 attribute_length; // 固定为 2u2 constantvalue_index; // 指向常量池中该字段的常量值(Integer、Float、Long、Double、String)}
- ConstantValue
- 方法级别属性(method_info 中的 attributes[])
-
code
Code_attribute {u2 attribute_name_index; // 必然指向 "Code"u4 attribute_length; // 属性总长度(不含前6字节)u2 max_stack; // 操作数栈最大深度u2 max_locals; // 局部变量槽个数u4 code_length; // 字节码长度(表示这个code[] 数组的字节数)u1 code[code_length]; // 字节码指令序列u2 exception_table_length; // 异常处理表长度{u2 start_pc;u2 end_pc;u2 handler_pc;u2 catch_type; // 指向常量池(异常类),0代表 catch all} exception_table[exception_table_length];u2 attributes_count;//属性表的个数attribute_info attributes[attributes_count]; // Code 的子属性,比如 LineNumberTable 等 }
code[length]中存的是字节码指令助记符号(JVM执行的机器码)
-
LineNumberTable
主要作用是 记录字节码指令与源代码行号的对应关系。便调试器定位当前执行的代码行,实现断点、单步调试等功能。LineNumberTable_attribute {u2 attribute_name_index; // 常量池中 "LineNumberTable" 的索引u4 attribute_length; // 属性长度u2 line_number_table_length; // 表项个数(有些人对这个属性名定义不同,但是是同一个东西){u2 start_pc; // 字节码偏移量(指令起始位置)u2 line_number; // 源代码行号} line_number_table[line_number_table_length];}
line_number_table_length+line_number_table[line_number_table_length]=info[attribute_length]
-
LocalVariableTable
LocalVariableTable_attribute {u2 attribute_name_index; // 常量池中 "LocalVariableTable" 字符串的索引u4 attribute_length; // 属性长度u2 local_variable_table_length; // 局部变量表项数{u2 start_pc; // 变量作用域开始的字节码偏移(相当于哪个字节码指令开始可以使用这个变量)u2 length; // 作用域长度(字节码偏移范围)u2 name_index; // 变量名,指向常量池Utf8u2 descriptor_index; // 变量类型描述符,指向常量池Utf8u2 index; // 局部变量表槽号} local_variable_table[local_variable_table_length];}
local_variable_table_length+local_variable_table[local_variable_table_length]=info[attribute_length]
-
MethodParameters
编译器(JDK 8+)如果开启 -parameters 参数,会在字节码里生成这个属性,保留方法参数名。反射时调用 Method.getParameters() 可以获得真实的参数名。MethodParameters_attribute {u2 attribute_name_index;u4 attribute_length;u1 parameters_count;{u2 name_index; // 参数名(常量池Utf8索引)u2 access_flags; // 参数修饰符,如 final, synthetic, mandated} parameters[parameters_count];}
parameters_count+parameters[parameters_count=info[attribute_length]
-