字节码的整体结构
字节码整体分为10个部分
所占字节 | 分类 | 备注 |
---|---|---|
4个字节 | Magic Number(魔数) | 正确字节码标识为,由Java之父定义,固定的是CAFE BABE |
2 + 2个字节 | Minor Version + Major Version | 编译的JDK版本号,Minor Version为小版本,Major Version为大版本。但不包括Update版本 |
2 + N个字节 | Constant Pool(常量池) | 并不是都是常量,而是指字节码中的一块数据字典,其他地方仅仅引用常量池内的地址。前2个字节是用来表示当前常量池的长度。常量池内常量个数为常量池长度 - 1,因为常量池是从下标1开始的。下标0是保留位,默认保存null。 |
2个字节 | Access Flag | 访问标识(ACC_Public、ACC_Static等) |
2个字节 | This Class Name | 类名(全路径限定名称),是一个引用直接引用常量池内的内容 |
2个字节 | Super Class Name | 父类名,是一个引用直接引用常量池内的内容 |
2 + N个字节 | Interfaces | 当前类所实现的全部接口 |
2 + N个字节 | Fields | 当前类所拥有的所有字段 |
2 + N个字节 | Methods | 当前类所拥有的所有方法(每个方法都会有一个属性叫Code,里面是该方法的指令集。一般来说每个Code还会带有2个属性,LineNumberTable和LocalVaribleTable来分别记录字节码对应源码的行号和方法的变量表) |
2 + N个字节 | Attrubites | 所有属性(最常见的就是Source File) |
在上表中,Fields、Methods等属性均是2 + N个字节,其中固定的2个字节表示的当前分类的元素个数,如果为0则不会存在后面的N个字节,而是直接进入下一个分类的内容。
描述符
另外对于常量池中还有一部分内容需要特别强调,JVM对每种数据类型及每个方法都存在对应的描述符用来精简字节码的结构和长度,下面给出常用的描述符
8种基本类型 + void
Java类型 | 描述符 |
---|---|
Byte | B |
Short | S |
Int | I |
Long | J |
Float | F |
Double | D |
Char | C |
Boolean | Z |
Void | V |
对象类型
L+对象的权限定名称(其中的.替换成/)+ ;
如Object的对象描述符为:Ljava/lang/Object;
String的对象描述符为:Ljava/lang/String;
数组类型
以 '[' 开头后面接数组的元素类型,如果是基本类型就是 [ + 对应的基本类型的描述符,如果是对象类型数组则是 [ + 对象描述符。示例如下:
基本类型:int[] -> [I, byte[] -> [B
对象类型:Integer[] -> [Ljava/lang/Integer; , String[]
方法的描述符
方法描述符是对方法的返回值+参数进行精简,对方法名并不产生影响。其结构是(方法参数类型描述符)+返回值类型的描述符。示例如下:
// 该方法的方法描述符为 (I)V
public void test(int a ){...}
// 该方法的方法描述符为 (ILjava/lang/Integer;)I
public int add(int a,Integer b){...}
Fields Table的结构
每个Field都是按照如下的数据结构来存储信息的。
属性长度 | 属性名 | 备注 |
---|---|---|
u2 | access_flags | 访问标识 |
u2 | name_index | 字段名在常量池中的索引 |
u2 | Descriptor_index | 字段描述符在常量池中的索引 |
u2 | Attributes_count | 属性个数 |
Attribute_info | 属性信息,是一个长度为Attributes_count的数组 |
Methods Table的结构
方法表的数据结构与字段表一样,需要额外说明的是Attribute_info的结构,从上表可以看出Attribute_info是一个长度为Attributes_count的数组,我们现在来分析数组内的数据结构。
属性长度 | 属性名 | 备注 |
---|---|---|
U2 | Attrubite_name_index | 属性名称在常量池中的索引 |
U4 | Attribute_length | 属性长度 |
U1 | Info[Attribute_length] | 属性信息 |
JVM一般会内置一些属性,但是也允许编译器自定义属性写入class文件,供运行时使用。其中最重要的一个属性是Code。下面我们会详细介绍Code属性。
Method Attribute之Code Attribute
Code属性是用来保存方法的结构(指令)。其数据结构如下所示:
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];// 异常表,表示在[start_pc,end_pc)范围内如果出现catch_type类型的异常则交由handler_pc来处理,当catch_type为0是表示处理所有异常
u2 attribute_count; // 属性个数
attribute_info attributes[attribute_count] // 属性信息
}
上述结构中大多数情况下attributes有2个,分别是前文提到的LineNumberTable和LocalVaribleTable。而LocalVaribleTable的长度与max_locals相等。
调用方法的指令
指令 | 说明 |
---|---|
Invokeinterface | 调用接口内的方法,实际上是在运行期决定的,决定到底调用那个实现的方法 |
Invokestatic | 调用静态方法 |
Invokespecial | 调用私有、构造方法及父类的方法 |
Invokevirtual | 调用虚方法,即需要在运行期动态查找的方法,和invokeinterface有点类似,但不是借口中的方法,而是类似于子类、方法重写等情况。 |
Invokedynamic | 调用动态方法 |
与上表中提到的虚方法对应的是非虚方法,他们是在编译器就能够决定的,在类加载阶段就能将符号引用转为直接应用。他们分别是:
- 静态方法
- 父类方法
- 构造方法
- 私有方法(无法被重写)
到此为止字节码模块相关内容就介绍到这里。由于本人能力有限,所表达的内容难免会有错误,欢迎大家评论或私信我指出,我们共同讨论进步。