java字节码结构
Class字节码中有两种数据类型
-
字节数据直接量—基本的数据类型
- u1—代表连续的1个字节组成的整体数据
- u2—代表连续的2个字节组成的整体数据
- u4—代表连续的4个字节组成的整体数据
- u8—代表连续的8个字节组成的整体数据
-
表(数组)
是由多个基本数据或其他表,按照既定顺序组成的大的数据集合
表是有结构的—体现在:组成表的成分所在的位置和顺序都是已经严格定义好的
使用javap -verbose命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息,类变量与成员变量信息
| 长度 | 类型 | 描述 | 数量 | 备注 |
|---|---|---|---|---|
| u4 | Magic | 魔数值为CAFEBABE16进制 | 1 | Java创始人James Gosling制定 |
| u2 | minor_version | 次版本号 | 1 | |
| u2 | major_version | 主版本号 | 1 | |
| u2 | constant_pool_count | 常量池个数 | 1 | |
| cp_info | constant_pool | 常量池表 | constant_pool_count-1 | |
| u2 | access_flags | 类的访问控制权限 | 1 | |
| u2 | this_class | 类名 | 1 | |
| u2 | super_class | 父类名 | 1 | |
| u2 | interfaces_count | 接口个数 | 1 | |
| u2 | interfaces | 接口名 | interfaces_count | |
| u2 | fields_count | 域个数 | ||
| field_info | fields | 域的表 | fields_count | |
| u2 | methods_count | 方法个数 | 1 | |
| method_info | methods | 方法表 | methods_count | |
| u2 | attributes_count | 附加属性个数 | 1 | |
| attribute_info | attributes | 附加属性的表 | attributes_count |
完整的Java字节码结构
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;
fields_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
1 魔数
所有.class文件的字节码文件的前4个字节都是魔数,魔数值为固定值—0xCAFEBABE
2 版本信息
分为主版本号和次版本号
以java version "1.8.0_131"为例
- 1.8表示主版本号
- 0表示次版本号
- 131表示更新号
主版本号对应的字节码信息
| 主版本号 | 字节码16进制 |
|---|---|
| 1.8 | 52 |
| 1.7 | 51 |
| 1.6 | 50 |
| 1.5 | 49 |
| 1.4 | 48 |
| 1.3 | 47 |
| 1.2 | 46 |
3 常量池
- 一个Java类中定义的很多信息都是有常量池类维护和描述的,占据字节码文件内容的很大一部分
- 可以将常量池看做是Class文件的资源仓库.比如
Java类中定义的方法与变量信息都是存在在常量池中 - 常量池中主要存储主要存储两类常量:
- 字面量
- 文本字符串
- Java中声明为final的常量值等
- 符号引用
- 类和接口的全局限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 字面量
3.1 常量池的总体结构
Java类所对应的常量池主要由常量池数量与常量池组这两部分构成
- 常量池数量紧跟在主版本数量之后,占据两个字节
- 常量池组则紧跟在常量池数据之后
- 常量池数组中不同元素的类型,结构都是不同的,长度自然也不同
- 每种元素的第一个数据都是
U1类型—该字节是个标志位,占据一个字节 - JVM在解析常量池时,会根据
U1类型来获取元素的数据结构
值得注意的是,常量池数组中元素的个数=常量池数-1(其中0暂时不使用)
- 目的是满足某些常量池索引值的数据在特定情况下需要表达
不引用任何一个常量池的含义 - 根本原因为索引为0也是一个常量,只不过它不位于常量表中,这个常量就对应null值,所以常量池的索引从1而非0开始
在JVM规范中,每个变量/字段都有描述信息
- 描述信息主要的作用是描述字段的数据类型,方法的参数列表(包括数量,类型与顺序)和返回值
3.2 描述符规则
3.2.1 基本数据类型和对象类型描述符规则
根据描述符规则
基本数据类型和代表无返回值的void类型都用一个大写字母来表示对象类型使用字符L+对象的全限定名称来表示
为了压缩字节码文件的体积,对于基本数据,JVM都只使用一个大写字母来表示,如下所示:
- B—byte
- C—char
- D—double
- F— float
- I—int
- J—long
- S—short
- Z—boolean
- V—void
- L—对象类型,如
Ljava/lang/String
3.2.2 数组类型的描述符规则
对于数组类型来说,每个维度使用一个前置的[来表示,如
int[]被记录为[iString[][]被记录为[[Ljava/lang/String
3.2.3 方法的描述符规则
用描述符描述方法时,按照先参数列表,后返回值的顺序来描述
- 参数列表按照参数的严格顺序放在
一组()之内
如方法
String getUserInfoByIdAndName(int id, String name){}
其描述符为
(I,Ljava/lang/String) Ljava/lang/String
| 常量 | 项目 | 类型 | 描述 |
|---|---|---|---|
| CONSTANT_utf8_info | tag | U1 | 值为1 |
| length | U2 | UTF-8编码的字符串长度 | |
| bytes | U1 | 长度为length的UTF-8编码的字符串 | |
| CONSTANT_integer_info | tag | U1 | 值为3 |
| bytes | U4 | 按照高位在前存储的int值 | |
| CONSTANT_float_info | tag | U1 | 值为4 |
| bytes | U4 | 按照高位在前存储的float值 | |
| CONSTANT_long_info | tag | U1 | 值为5 |
| bytes | U8 | 按照高位在前存储的long值 | |
| CONSTANT_Double_info | tag | U1 | 值为6 |
| bytes | U8 | 按照高位在前存储的long值 | |
| CONSTANT_class_info | tag | U1 | 值为7 |
| index | U2 | 指向全限定名常量项的索引 | |
| CONSTANT_String_info | tag | U1 | 值为8 |
| index | U2 | 指向字符串字面量的索引 | |
| CONSTANT_Fieldref_info | tag | U1 | 值为9 |
| index | U2 | 指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项 | |
| index | U2 | 指向字段描述符CONSTANT_NameAndType_info的索引项 | |
| CONSTANT_Methodref_info | tag | U1 | 值为10 |
| index | U2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
| index | U2 | 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
| CONSTANT_Methodref_info | tag | U1 | 值为11 |
| index | U2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引项 | |
| index | U2 | 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
| CONSTANT_NameAndType_info | tag | U1 | 值为12 |
| index | U2 | 指向该字段或方法名称常量项的索引 | |
| index | U2 | 指向该字段或方法描述符常量项的索引 |
表2中描述了11中数据类型的结构,其实在JDK1.7之后又新增了三种
- CONSTANT_MethodHandle_info
- CONSTANT_MethodTypeInfo
- CONSTANT_InvokeDynamic_info
所以一共是14种
4 访问标志
长度为2个字节,访问标志信息包括
- 该Class文件是类还是接口
- 是否被定义为public
- 是否是abstract
- 如是类是否被声明成final
表3. Class access and property modifiers–类访问和属性修饰符
| 标志名 | 值 | 说明 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 声明为public,可以从其包外部访问 |
| ACC_PRIVATE | 0x0002 | 声明为private,仅共当前类访问 |
| ACC_STATIC | 0x0008 | 声明为static |
| ACC_FINAL | 0x0010 | 声明为final,不允许被子类继承 |
| ACC_SUPER | 0x0020 | 当调用invokespecial指令时相应的去调用父类的构造方法 |
| ACC_INTERFACE | 0x0200 | 声明为interface接口,不是类 |
| ACC_ABSTRACT | 0x0400 | 声明为abstract,不能被实例化 |
| ACC_SYNTHETIC | 0x1000 | 声明为synthetic,源代码中不存在 |
| ACC_ANNOTATION | 0x2000 | 声明为annotation注解类型。 |
| ACC_ENUM | 0x4000 | 声明为enum枚举 |
5 字段表filed_info集合
字段表用于描述类和接口中声明的变量
这里的字段包含了
- 类级别变量
- 实例变量
但是不包括方法内部声明的局部变量
表4. 字段表filed_info结构
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | access_flags—访问标志 | 1 |
| u2 | name_index—字段名索引 | 1 |
| u2 | descriptor_index—描述符索引 | 1 |
| u2 | attribute_count—附加属性个数 | 1 |
| attribute_info | attributes | attribute_count |
完整的filed_info字节码结构
field_info{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attribute_count;
attributes[attribute_count];
}
6 方法表method_info集合
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | access_flags—访问表示 | 1 |
| u2 | name_index—字段名索引 | 1 |
| u2 | descriptor_index—描述符索引 | 1 |
| u2 | attribute_count—附加属性个数 | 1 |
| attribute_info | attributes | attribute_count |
完整的method_info字节码结构
method_info{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attribute_count;
attributes[attribute_count];
}
6.1 附加属性表attribute_info集合
- 方法中的每个属性都是一个attribute_info结构
- JVM预定义了部分
attribute,但是编译器自己也可以实现自己的attribute写入class文件里,共运行时使用 - 不同的
attribute通过attribute_name_index来区分
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | attribute_name_index—属性名索引 | 1 |
| u4 | attribute_length—属性名长度 | 1 |
| u2 | info[attribute_length]—属性信息 | attribute_length |
完整的attribute_info字节码结构
attribute_info{
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
6.2 Code attribute结构
Code attribute的作用是保存该方法的结构
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | attribute_name_index—属性名索引 | 1 |
| u4 | attribute_length—attribute所包含的字节数.不包含attribute_name_index和attribute_length | 1 |
| u2 | max_stack—最大栈深度,表示这个方法运行时的任何时刻所能到达的操作数栈的最大深度 | 1 |
| u2 | max_locals—最大局部变量数表示这个方法执行期间所创建的局部变量的数目,包含用来表示传入的参数的局部变量 | 1 |
| u4 | code_length–该方法所包含的字节码的字节数以及具体的指令码1 | 1 |
| u1 | code[code_length] | code_length |
| u2 | exception_table_length—异常表长度 | 1 |
| exception_table | exception_table[exception_table_length]异常表—存放的是处理异常的信息2 | exception_table_length |
| u2 | attributes_count—异常表长度 | 1 |
| attributes | attributes[attributes_count]—异常表长度 | attributes_count |
如所对应的字节码
Code_attribute{
u2 attribute_name_index;
u4 attribute_length;
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;
attributes[attributes_count];
}
start_pc和end_pc表示在code数组中从start_pc到end_pc(包含前者,不包含后者)的指令抛出的异常会由这个表项来处理handler_pc表示处理异常的代码的开始处.catch_type表示会被处理的异常类型,它指向常量池里的一个类,当catch_type为0时,表示处理所有异常
Java字节码对异常的处理方式
- 统一采用异常表的方式来对异常进行处理
- 在
JDK 1.4.2之前的版本中,并不是使用异常表的方式来对异常进行处理的,而是采用特定的指令方式 - 当异常存在
finally语句块时,现代化的JVM采取的处理方式是将finally语句块的字节码拼接到每一个catch块后面,换句话说,程序中至少存在多个catch块,就会在每一个catch语句块后面重复多少个finally语句块的字节码
[1] 具体字节码即是该方法被调用时,虚拟机所执行的字节码
[2] 每个exception_table表项有start_pc, end_pc, handler_pc, catch_pc组成
6.3 LineNumberTable的结构
LineNumberTable{
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]
}
6.4 LocalVariableTable的结构
对于Java类的每一个实例方法(非static方法),其在编译后所生成的字节码当中,
方法参数的数量总是会比源代码中方法参数的数量多一个(this).
它位于方法的第一个参数位置处,这样我们就可以像Java的实例方法中使用this来访问当前对象的属性以及其他方法
这个操作是在编译期间完成的,即由javac编译器在编译时将对this的方法转化为对一个普通实例方法参数的访问
接下来在运行期间,由JVM在调用实例方法时,自动向实例方法传入该this参数.
所以.在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量
LocalVariableTable{
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{
u2 start_pc;
u2 length;
u2 name_index;
u2 descripor_index;
u2 index
}local_variable_table[local_variable_table_length]
}
7 SourceFile的结构
SourceFile{
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
本文深入探讨Java字节码的结构,详细讲解Class文件的组成部分,包括魔数、版本信息、常量池、访问标志、字段表、方法表及附加属性等。解析字节码如何描述类和接口信息,以及常量池中各种常量的存储规则。
247

被折叠的 条评论
为什么被折叠?



