字节码
简介
- 编译器将Java源码编译成符合Java虚拟机规范的字节码文件。
- 字节码组成结构比较特殊,其内部不包含任何分隔符区分段落。
- 一组8位字节单位的字节流组成了一个完整的字节码文件。
字节码内部组成结构
- 《Java虚拟机规范 Java SE7》中,每一个字节码文件都对应着全局唯一的一个类或者接口的定义信息。
- 本书用项(item)表示用于描述类结构格式的内容
- 每一项包括类型、名称以及该项的数量。
- 各项包含在字节码文件中,按严格的顺序连续存放。
- 该结构只有两种数据结构,分别为无符号数和表。
类型 | 说明 |
u1 | 1个字节,无符号类型 |
u2 | 2个字节,无符号类型 |
u4 | 4个字节,无符号类型 |
u8 | 8个字节,无符号类型 |
- 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表的后缀都是_info,并且字节码文件实质上就是一张表。
-
每个字节码文件对应着一个ClassFile文件的结构,如下:
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];
}
ClassFile结构描述
- ①magic
- 一个有效的字节码文件的前四个字节为0xCAFEBABE,称为magic number
- ②minor_version(次版本号)和major_version(主版本号)
- 如果主版本号为M,次版本号为m,那么字节码文件版本号为M.m
- 低版本的JVM不能处理由高版本JVM编译生成的字节码,会抛出java.lang.UnSupportedClassException,不过高版本的JVM可以兼容由低版本JVM编译出来的字节码文件。
- ③constant_pool_count(常量池计数器)和constant_pool(常量池)
- 常量池是字节码文件中非常重要的数据项,也是与其他项关联最多和占用字节码空间最大的数据项。
- 常量池主要用于存放字面量(Literal)和符号引用(Symbolic References)两大类数据常量,其访问方式是通过索引来进行访问的。
- 常量池中存放的字面量由文字字符串、final常量值等构成,而符号引用则包括了类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor),以及方法的名称和描述符。
类型 | tag | 描述 |
CONSTANT_Utf8_info | 1 | UTF-8编码的字符串 |
CONSTANT_Integer_info | 3 | 整形字面值 |
CONSTANT_Float_info | 4 | 单精度浮点数类型字面值 |
CONSTANT_Long_info | 5 | 长整型字面值 |
CONSTANT_Double_info | 6 | 双精度浮点类型字面值 |
CONSTANT_Class_info | 7 | 类或者接口的符号引用 |
CONSTANT_String_info | 8 | 字符串类型的字面值 |
CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
CONSTANT_Methodref_info | 10 | 方法的符号引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 表示方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
- access_flags(访问标志)
- 访问标志主要用于表示某个类或者某个接口的访问权限
- 访问标志一共有16个可以用,目前只用了8个,对于没有被用到的标志,编译器要求设置为0,并且Java虚拟机必须忽略它。
标志名称 | 值 | 描述 |
ACC_PUBLIC | 0X0001 | 声明为public,可以被包外的类访问 |
ACC_FINAL | 0X0010 | 声明为final,不允许有派生类 |
ACC_SUPER | 0X0020 | 当用到invokespecial指令时,需要特殊处理的超类方法 |
ACC_INTERFACE | 0X0200 | 标志定义的是接口而不是类 |
ACC_ABSTRACT | 0X0400 | 声明为abstract,不能够被实例化 |
ACC_SYNTHETIC | 0X1000 | 声明为SYNTHETIC,标志并非java代码生成的 |
ACC_ANNOTATION | 0X2000 | 标志注解类型 |
ACC_ENUM | 0X4000 | 标志枚举类型 |
- ⑤this_class(类索引)和super_class(超类索引)
- 类索引和超类索引各自通过索引指向常量池列表中类型为CONSTANT_Class_info的常量项。
- CONSTANT_Class_info的常量项由tag和name_index构成。
- tag值为Constant_Class_info(7)值的常量。name_index指向常量池中类型为CONSTANT_Class_info的索引。该索引可以获得该类的全限定名。
- 简单来说,类引用用于确定当前类的全限定名,超类引用用于确定超类的全限定名。
- ⑥ interfaces_count(接口计数器)和interfaces(接口计数器)
- interfaces_count用于记录当前类或者接口的直接超类接口的数量。
- 计数从0开始。
- 接口表是一个索引集合,包含了当前类或者接口在常量池列表中直接超类接口的索引集合,通过这个索引即可找到当前类或者接口的全限定名。
- ⑦fields_count(字段计数器)和fields(字段表)
- 字段计数器用于表示一个字节码文件中field_info表总数,也就是一个类中类变量和实例变量 的总个数。
- 字段表是一个数组集合,每一项都是一个field_info的数据项
- field_info用于表示一个字段的完整信息。
- ⑧methods_count(方法计数器)和methods(方法表)
- methods_count用于记录字节码文件中method_info的个数,methods是一个集合,每个元素都是method_info。
- method_info用于表示一个方法发完整信息。
- ⑨ attributes_count(属性计数器)和attributes(属性表)
- 属相计数器用于表示当前字节码文件中attribute_info表的个数,属性表是一个集合,元素是attr_info表。
- 属性表可以出现在Class_file表、fields(字段表)、methods(方法表)中,用于表示相关信息。以上出现的表都是组合数据结构,可能由attribute_info组成。
符号引用
简介
- 常量池主要存放字面量(Literal)和符号引用(Symbol Reference)两大类数据常量。符号引用是由3中特殊的字符串构成,分别是:全限定名、简单名称、描述符。
- 字节码文件中全限定名可以用于描述类和接口。
类或者接口的全限定名
- 字节码文件中所以的类或者接口都是通过全限定名的方式进行表示的。
- 如:java.lang.Object类的全限定名为java/lang/Object
- java.util.Map全限定名为java/util/Map
简单名称
- 类中的字段或者方法都是以简单名称的来进行存储的。
- 如String类中的toString方法是以toString。
- 字段或者方法的简单名称中不能包含. : [ /等以ASCII和Unicode字符的表示形式。
描述符
- 方法和字段均有描述符,分别称为方法描述符,字段描述符。
字段描述符
- 字段描述符分为BaseType、ObjectType、ArrayType.解释信息如下表所示:
描述符 | 类型 | 描述符含义 |
B | byte | 字节 |
C | char | 字符 |
D | double | 双精度浮点数 |
F | float | 单精度浮点数 |
I | int | 整形 |
J | long | 长整形 |
S | short | 短整形 |
Z | boolean | 布尔类型 |
L | reference | 对象类型 |
[ | reference | 数组类型 |
- 字段描述符的格式:
- BaseType:
- B
- C
- …
- ObjectType
- L className
- ArrayType
- [ componetType
- BaseType:
- 例如:Ljava/lang/String表示一个java.lang.String类型的实力对象。
- [C–表示存储char类型常量的数组。[[C表示二维数组
方法描述符
- 方法描述符格式
- (ParameterDescriptor*)ReturnDescriptor
- ParameterDescriptor:FieldType
- ReturnDescriptor:FieldTye/VoidDescriptor(V)如果是返回void,用V表示
- 方法描述符包含了参数描述符和返回值描述符,参数描述符表示参数类型和个数,返回值描述符表示返回值信息。
- 例如java.lang.Object getInfo(int a,String name,boolean sex)–>(ILjava/lang/StringZ)Ljava/lang/Object.(注意.换成/).
- java虚拟机规范中,一个方法描述符如果是有效的,那么他对应的方法参数列表总长度应该小于225.
常量池
简介
- 常量池列表的常量数并不固定,需要一个2字节的计数器来记录常量池列表的常量数。
- Java7 中有14中类型的常量项。
-
常量项基本格式如下:
cp_info{
u1 tag;
u1 info[];
}
CONSTANT_Utf8 _info常量项
-
表结构
CONSTANT_Utf8_info{
u1 tag;
u2 length;
u1 butes[length];
} CONSTANT_Utf8 _info是一种改进过的UTF8编码格式的用于存储如文字字符串、类或者接口的全限定名、字段或者方法的简单名称以及描述符等常量字符串信息。
- tag项为1。
- length表示后续数组长度。
- bytes[]用于存储字符串信息的byte数组。
CONSTANT _Intger _info
-
表结构
CONSTANT _Intger _info{
u1 tag;
u4 bytes;
} tag==3
- bytes使用4字节存储整型值。大端顺序存储。
CONSTANT _Float _info
-
表结构
CONSTANT _Float _info{
u1 tag;
u4 bytes;
} tag==4
- bytes使用4字节存储单精度浮点数值。大端顺序存储。
CONSTANT _Long _info
-
表结构
CONSTANT _Long _info{
u1 tag;
u8 bytes;
} tag==5
- bytes使用8字节存储长整形数值。大端顺序存储。
CONSTANT _Double _info
-
表结构
CONSTANT _Double _info{
u1 tag;
u8 bytes;
} tag==6
- bytes使用8字节存储双精度浮点数值。大端顺序存储。
CONSTANT _Class _info
-
表结构
CONSTANT _Class _info{
u1 tag;
u2 name_index;
} tag==7
- name_index指向常量池列表中一个类型为CONSTANT_Utf8_info的常量项。,通过这个常量项即可找到一个类或者接口的全限定名字符串。
CONSTANT _String _info
-
表结构
CONSTANT _String _info{
u1 tag;
u2 string_index;
} tag==8
- string_index指向常量池列表中一个类型为CONSTANT_Utf8_info的常量项。,通过这个常量项即可找到文字字符串。
CONSTANT _Fieldref _info
-
表结构
CONSTANT _Fieldref _info{
u1 tag;
u2 class_index;
u2 name _and _type _index;
} tag==9
- class_index指向常量池列表中一个类型为CONSTANT_Class_info的常量项。,通过这个常量项即可找到当前字段所属的类或者接口。
- name _and _type _index则指向一个NameAndType常量项,通过该索引即可找到该字段的简单名称和字段描述符。
CONSTANT _Methodref _info
-
表结构
CONSTANT _Methodref _info{
u1 tag;
u2 class_index;
u2 name _and _type _index;
} tag==10
- class_index指向常量池列表中一个类型为CONSTANT_Class_info的常量项。,通过这个常量项即可找到当前方法所属的类或者接口。
- name _and _type _index则指向一个NameAndType常量项,通过该索引即可找到该方法的简单名称和方法描述符。
CONSTANT _InterfaceMethodref _info
-
表结构
CONSTANT _InterfaceMethodref _info{
u1 tag;
u2 class_index;
u2 name _and _type _index;
} tag==11
- class_index指向常量池列表中一个类型为CONSTANT_Class_info的常量项。,通过这个常量项即可找到当前方法所属的接口。
- name _and _type _index则指向一个NameAndType常量项,通过该索引即可找到该方法的简单名称和方法描述符。
CONSTANT _NameAndType _info
-
表结构
CONSTANT_Fieldref_info{
u1 tag;
u2 class_index;
u2 descriptor_index;
} tag==12
- class_index指向常量池列表中一个类型为CONSTANT _Utf8 _info的常量项。,通过这个常量项即可找到当前字段或者字段的简单名称。
- descriptor_index则指向一个CONSTANT _Utf8 _info常量项,通过该索引即可找到当前字段或者方法的描述符。
CONSTANT _MethodHandle _info
-
表结构
CONSTANT _MethodHandle _info{
u1 tag;
u2 reference_kind;
u2 reference_index;
} CONSTANT _MethodHandle _info常量项主要用于表示方法句柄。
- tag==15
- reference_index指向常量池列表中一个有效索引。通过这个索引即可找到相关信息。
- reference_kind项的值必须在1~9之间,它决定了后续reference_index的方法句柄类型。该类型值表示方法句柄的字节码行为。如果reference_kind为1、2、3、4,那么reference_index项中的值指向常量池列表中CONSTANT_Fieldref_info常量项的索引,表示为一个字段创建的句柄。如果reference_kind在5、6、7、8中,那么reference_index中的值指向常量池列表中CONSTANT_Methodref_info常量项,表示为一个方法创幻的句柄。如果reference_kind为9,那么reference_index中的值指向常量池列表中CONSTANT_InterfaceMethodref_info的常量项,表示为一个接口中的方法创建的句柄。
CONSTANT _MethodType _info
-
表结构
CONSTANT _MethodType _info{
u1 tag;
u2 descriptor_index;
} - tag==16
- descriptor_index的值是一个指向常量池列表中CONSTANT_Utf8_info的常量项的索引,表示方法的描述符。
CONSTANT _InvokeDynamic _info
-
表结构
CONSTANT _InvokeDynamic _info{
u1 tag;
u2 bootstrap_method_attr_index;
u2 name _and _type _index;
} - CONSTANT _InvokeDynamic _info常量项其用途就是给invokedynamic指令指定启动方法(bootstrap method)等信息。
- tag==16
- bootstrap_method_attr_index项的值必须是当前字节码文件中引导方法的bootstrap_method数组的有效索引。
- name _and _type _index则指向一个NameAndType常量项,通过该索引即可找到该方法的简单名称和方法描述符。
字段表fields
- 未完待续
方法表methods
-
*