浅析Java字节码文件
字节码文件
- 1.概要
- 2.字节码文件各组成项(item)含义分析
- (1)magic(魔数)
- (2)major_version 、minor_version(版本号)
- (3)constant_pool_count(常量池计数器)与constant_pool[](常量池)
- (4)access_flags (访问标识)
- (5)this_class (类索引)
- (6)super_class (父类索引)
- (7)interfaces_count (接口计数器)与interfaces[](接口表)
- (8)fields_count(字段计数器)与fields[](字段表)
- (9)methods_count(方法计数器)与methods[](方法表)
- (10)attributes_count(属性计数器)与 attributes[](属性表)
1.概要
java程序是虚拟机上运行,而虚拟机JVM实际运行的是字节码文件,且此字节码文件的源文件不一定是Java源代码,只要编译后的字节码文件符合Java虚拟机的规范即可。
字节码文件没有任何分隔符分隔区段,每个组成项(item)只用两个字节(0-65536)的无符号数(u2)标识此项的在字节码文件中所占用的字节数,各组成项(item)以Big-Endian顺序(大端,即按高位字节在地址最低位,最低字节在地址最高位来存储数据)连续存放,当超出这个字节数长度便是另外一项(item)内容,所以其内部结构是相当紧凑的。
每一项(item)都包括类型、名称、及其数量。类型可以是表名,也可以“基本类型”(即无符号数),字节码文件只有无符号数、表(Table)这两种数据结构。无符号数有u1 、u2、 u4、u8这四种,而表则是由无符号数及其他表组成的复合数据类型,以“_info”后缀结尾,整个字节码文件也可看成一张(很大的)表。
(1)基本类型(无符号数)
类型 | 说明 |
---|---|
u1 | 1字节的无符号数 |
u2 | 2字节的无符号数 |
u4 | 4字节的无符号数 |
u8 | 8字节的无符号数 |
(2)字节码文件结构
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];
}
2.字节码文件各组成项(item)含义分析
(1)magic(魔数)
magic,魔数是字节码文件开始的4个字节,魔数值固定为 0xCAFEBABE,不会改变。魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件,如果开头不是0xCAFEBABE,JVM将认为该文件不是.class字节码文件而拒绝解析。
(2)major_version 、minor_version(版本号)
major_version 、minor_version,分别是主版本号、次版本号,它们在magic之后,各占两个字节。随着Java更新换代,不断扩充新特性,字节码文件的格式也随着变化。字节码文件的版本号确定了特定的字节码文件格式,只有给定了版本号JVM才能正确读取字节码文件。如果其版本号超出了JVM能处理的有效范围,JVM则不会处理加载些字节码文件。如高版本JDK编译的class文件不能在低版本的JVM中运行,而高版本的JVM却能向下兼容运行低版本JDK编译的class文件。
(3)constant_pool_count(常量池计数器)与constant_pool[](常量池)
- constant_pool_count
常量池计数器 的计数值从1开始,因此计数器值等于 constant_pool 表中的成员数加 1。虽然常量计数器并没有将0作为计数值,但索引0却还是存在的,主要是为了满足后续其他项在不引用常量池中的任何常量时,默认可以所常量池中的访问索引设为0来表示。 - constant_pool[]
常量池是字节码文件中非常重要的数据项,与其他项关联最多和占用空间最大的数据项。常量池主要存放字面量(Literal)和符号引用(Symbolic references)两类数据常量,其他项以索引的方式来访问常量池。- 常量池基本结构
常量池数组紧跟常量池计数器之后,与一般数组概念不同,常量池数组中不同元素的类型、结构都是不同的,长度也不同,但每一元素的第一个数据tag都是一个u1类型,此字节是个标志位,根据tag的值确定该元素的具体类型。
- 常量池基本结构
1)常量池分布
2)常量池组成元素
不同元素之间,其结构与类型是不同的,JVM一共定义了11种常量,并针对不同的常量进行专门的解析读取。
类型/含义 | 结构 | 类型 | 说明 |
---|---|---|---|
CONSTANT_Utf8_info UTF-8编码的字符串 |
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 | 按照高位在前(低地址位置)储存double值 | |
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_InterfaceMethodref_info 接口中方法的符号引用 |
tag | u1 | 值为11 |
index | u2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引 | |
index | u2 | 指向方法名及类型描述符CONSTANT_NameAndType_info的索引 | |
CONSTANT_NameAndType_info 字段或方法的部分符号引用 |
tag | u1 | 值为12 |
index | u2 | 指向该字段名或方法名常量项的索引 | |
index | u2 | 指向该字段或方法描述符常量项的索引 |
(4)access_flags (访问标识)
access_flags,访问标识是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。
- 访问和修饰符
标记名 | 值 | 含义 |
---|---|---|
ACC_FINAL | 0x0010 | 不允许有子类 |
ACC_PUBLIC | 0x0001 | 可以被包的类外访问。 |
ACC_SUPER | 0x0020 | 当用到 invokespecial 指令时,需要特殊处理的父类方法。 |
ACC_INTERFACE | 0x0200 | 标识定义的是接口而不是类。 |
ACC_ABSTRACT | 0x0400 | 不能被实例化。 |
ACC_ANNOTATION | 0x2000 | 标识注解类型 |
ACC_ENUM | 0x4000 | 标识枚举类型 |
(5)this_class (类索引)
this_class,类索引的值必须是对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的项必须CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类或接口。
(6)super_class (父类索引)
- 类
对于类来说,super_class父类索引必须是对是onstant_pool 表中项目的一个有效索引值(Object除外,它没有父类,父类索引为0)。 constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类的直接父类。当前类的直接父类,以及它所有间接父类的 access_flag 中都不能带有 ACC_FINAL 标记。 - 接口
对于接口来说,super_class父类索引项的值必须是对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的项必须为代表 java.lang.Object 的 CONSTANT_Class_info 类型常量。
(7)interfaces_count (接口计数器)与interfaces[](接口表)
- interfaces_count
接口计数器的值表示当前类或接口的直接父接口数量。 - interfaces[]
接口表的每个成员的值必须是一个对 constant_pool 表中项目的一个有效索引值,它的长度为 interfaces_count。每个成员interfaces[i] 必须为 CONSTANT_Class_info 类型常量。成员所表示的接口顺序和对应的源
代码中给定的接口顺序(从左至右)一样.
(8)fields_count(字段计数器)与fields[](字段表)
- fields_count
字段计数器,表示当前 Class 文件 fields[]数组的成员个数。 - fields[]
字段表,fields[]数组中的每个成员都必须是一个 fields_info 结构的数据项,用于表示当前类或接口中某个字段的完整描述。fields[]数组描述当前类或接口声明的所有字段,但不包括从父类或父接口继承的部分。
(9)methods_count(方法计数器)与methods[](方法表)
- methods_count
方法计数器,表示当前 Class 文件 methods[]数组的成员个数。 - methods[]
方法表,methods[]数组中的每个成员都必须是一个 method_info 结构的数据项,用于表示当前类或接口中某个方法的完整描述。如果某个 method_info 结构的access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志,那么它所对应的方法体就应当可以被 Java 虚拟机直接从当前类加载,而不需要引用其它类。method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法。methods[]数组只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
(10)attributes_count(属性计数器)与 attributes[](属性表)
- attributes_count
属性计数器,表示当前 Class 文件 attributes 表的成员个数。 - attributes[]
属性表,attributes[] 表的每个项的值必须是 attribute_info 结构。Class 文件结构中的 attributes 表的项包括下列定义的部分或全部属性:InnerClasses、EnclosingMethod、Synthetic、Signature、SourceFile,SourceDebugExtension、Deprecated、RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations以及BootstrapMethods属性。