JVM-类文件结构

Class类文件结构

Class文件是一组以8位字节为基础单位的二进制流;

Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表;

无符号数属于基本的数据类型,以u1,u2,u4,u8来分别代表1个字节,2个字节,4个字节和8个字节的无符号数, 无符号数可以用来描述数字,索引引用,数量值或者按照UTF-8编码构成字符串值;

表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以"_info"结尾.表用于描述有层次关系的复合结构的数据, 整个Class文件本质上就是一张表;

Class文件中数据项目严格按照顺序紧凑地排列,没有任何分隔符号,所以所有的数据项,无论是顺序还是数量,甚至于数据存储的字节序这样的细节,都是被严格限定的.哪个字节代表什么含义,长度是什么,先后顺序如何,都不允许改变;

类型名称数量
u4magic(魔数)1
u2minor_version(次版本号)1
u2major_version(主版本号)1
u2constant_pool_count(常量池数量)1
cp_infoconstant_pool(常量池)constant_pool_count
u2access_flags(访问标志)1
u2this_class(当前类)1
u2super_class(父类)1
u2interfaces_count(接口计数)1
u2interfaces(接口集合)interfaces_count
u2fields_count(字段计数)1
field_infofileds(字段表集合)fields_count
u2methods_count(方法计数)1
method_infomethods(方发表集合)methods_count
u2attributes_count(属性计数)1
attribute_infoattributes(属性表集合)attributes_count

魔数与Class文件的版本

使用十六进制编辑器打开Class文件

1

魔数(magic):每个Class文件的头4个字节称为魔数,它的唯一作用是确定这个文件是否为一个能被虚拟机接收的Class文件.它的的值固定为0xCAFEBABE;

版本(minor_version,major_version):紧接着魔数的4个字节存储的是Class文件的版本号; 第5和第6个字节是次版本号, 第7和第8是主版本号.图示中的值为0x00000034, 换算十进制是52,对应的是JDK1.8;

常量池

常量池(constant_pool):可以理解为Class文件之中的资源仓库, 他是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目;

常量池入口(constant_pool_count):紧接着主次版本号之后的就是常量池入口(第9个字节开始), 常量池的入口需要放置一项u2类型(占用2个字节)的数据,代表常量池容量的计数值(constant_pool_count),此计数值是从1开始,因为第0项常量空出来是有特殊考虑的, 目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达"不引用任何一个常量池项目"的含义; 分析图中的常量池入口值0x004E,换算十进制为78,则说明此类的常量池中有77个常量;

常量池主要存放两大类常量: 字面量和符号引用.字面量比较接近于JAVA语言层面的常量概念, 如文本字符串,声明为final的常量值等.而符号引用则属于编译原理方面的概念,包括类和接口的全限定名,字段的名称和描述符,方法的名称和描述符;

标志位(tags):常量池中每一项常量都是一个表,设计中定义了十四种不同表结构,用于代表不同的常量类型;这些表结构都有一个共同特点,就是表开始的第一位是一个u1类型的标志位(tags), 用于代表当前这个常量属于哪种常量类型;

分析图示中第一个常量(第11个字节开始)的Tags, 值为0x0A,那么代表此类的第一个常量是CONSTANT_Methodref_info类型;根据表结构分析, 接下来有两个u2类型的index的值分别指向其他索引, 0x0013和0x002E分别指向索引为19, 46的常量池项;具体19,46的常量池是什么值需要往下继续分析;

常量池十四种表结构
常量类型结构项目-项目类型-描述
CONSTANT_Utf8_info
tagu1标志值=1(0x01)
lengthu2UTF-8编码的字符串占用的字节数
bytesu1长度为length的UTF-8编码的字符串
CONSTANT_Integer_info
tagu1标志值=3(0x03)
bytesu4按照高位在前存储的int值
CONSTANT_Float_info
tagu1标志值=4(0x04)
bytesu4按照高位在前存储的float值
CONSTANT_Long_info
tagu1标志值=5(0x05)
bytesu8按照高位在前存储的long值
CONSTANT_Double_info
tagu1标志值=6(0x06)
bytesu8按照高位在前存储的double值
CONSTANT_Class_info
tagu1标志值=7(0x07)
indexu2指向全限定名常量项的索引
CONSTANT_String_info
tagu1标志值=8(0x08)
indexu2指向字符串字面量的索引
CONSTANT_Fieldref_info
tagu1标志值=9(0x09)
indexu2指向声明字段的类或接口描述符CONSTANT_Class_info的索引项
indexu2指向字段描述符CONSTANT_NameAndType_info的索引项
CONSTANT_Methodref_info
tagu1标志值=10(0x0A)
indexu2指向声明方法的类描述符CONSTANT_Class_info的索引项
indexu2指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_Interfacemethodref_info
tagu1标志值=11(0x0B)
indexu2指向声明方法的类描述符CONSTANT_Class_info的索引项
indexu2指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_NameAndType_info
tagu1标志值=12(0x0C)
indexu2指向该字段或方法名称常量项的索引
indexu2指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_info
tagu1标志值=15(0x0F)
reference_kindu1值必须在1-9之间,包括1,9,它决定了方法句柄的类型,方法句柄类型的值表示方法句柄的字节码行为
reference_indexu2值必须是对常量池的有效索引
CONSTANT_MethodType_info
tagu1标志值=16(0x10)
descriptor_indexu2值必须是对常量池的有效索引,常量池在该索引处的响必须是CONSTANT_UTF8_info结构,表示方法的描述符
CONSTANT_InvokeDynamic_info
tagu1标志值=18(0x12)
bootstrap_method_attr_indexu2值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引
name_and_type_indexu2值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述

JDK的bin目录中,存在一个专门分析Class文件字节码的工具:javap, 使用命令javap -verbose xxx.class 可以查看字节码内容;

下图可以看出之前对常量池的分析是正确的, 此Class文件存在77个常量, 其中第一个常量为CONSTANT_Methodref_info类型,它指向索引值为19和46的常量;

 访问标志

访问标志(access_flags):用于识别类或者接口层次的访问信息, 包括这个Class文件是类还是接口,是否被定义为public类型,是否定义为abstract类型;如果是类是否被声明为final等;

访问标志是一个u2类型的数据项,常量池结束后,紧接着的两个字节就代表着访问标志的值;

官方定义中访问标志一共有十六种标志位可以使用, 当前只定义了其中8个, 没有使用到的标志位要求一律为0.将所有使用到的标志位做或位运算得到的最后的值即为访问标志的值;

类或者接口层次的访问标志种类
标志名称标志值描述
ACC_PUBLIC0x0001是否为public类型
ACC_FINAL0x0010是否被声明为final, 只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.02发生过改变,为了区别这条指令使用哪种语意,JDK1.02之后编译出来的类的这个标志都必须为真
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,此标志为真,其他类为假
ACC_SYNTHETIC0x1000标识这个类并非有用户代码产生的
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举

已知当前Class文件的类StringTest.class是一个普通的类,那么应该对应的是ACC_PUBLIC和ACC_SUPER两个标志, 0x0001|0x0020=0x0021, 与字节码文件中显示的一致;
 

 再看另一个Class文件, 已知此文件是一个普通的枚举类型, 枚举类型的实现就是一个final的类, 那么它的标志应该是ACC_PUBLIC、ACC_SUPER、ACC_FINAL、ACC_ENUM,即0x0001|0x0010|0x0020|0x4000=0x4031, 与字节码中文件显示的一致;

 类索引,父类索引与接口索引集合

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,接口索引集合(interfaces)是一组u2类型的数据集合,Class文件中由这三项数据来确定这个类的继承关系.

类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量, 通过CONSTANT_CLASS_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中全限定名字符串.

对于接口索引集合,入口第一项是一个u2类型的数据,作为接口计数器(interfaces_count),表示索引表的容量.如果该类没有实现任何接口,则该计数器的值为0,后面接口的索引表不再占用任何字节.

 依旧分析之前的Class文件, 如下图, 在访问标志之后就是类索引 0x000E=14, 指向索引为14的常量, 根据javap -v命令可以看到索引为14的常量是CONSTANT_CLASS_info类型, 此常量指向索引为62的常量, 而索引为62的常量是CONSTANT_Utf8_info类型常量, 它的值为com/bryan/study/baseclass/StringTest, 那么表明this_class是com/bryan/study/baseclass/StringTest; 同理分析父类索引, 0x0013=19, 父类索引指向索引为19的常量,最终指向索引为66的常量java/lang/Object, 那么当前类的父类就是java/lang/Object;分析接口索引集合, 当前文件中接口索引集合计数器为0, 那么表示当前类没有实现任何接口;

 字段表集合

字段表数量(field_count):紧跟接口集合后面的一个u2类型的数据,用于表示类的字段数量;

字段表(field_info): 用于描述接口或者类中声明的变量.

字段包括类变量和实例变量,但不包括方法内的局部变量.字段表包括字段的各种修饰符,使用u2类型的标志位来表示.字段的名称以及数据类型使用一个指向常量池索引的u2类型的值来表示; 字段表的整体结构包括 access_flags,name_index,descriptor_index,attributes_count,attributes;

字段表结构
类型名称数量
u2access_flags(访问标志)1
u2name_index(名称在常量池的索引)1
u2decsriptor_index(描述在常量池的索引)1
u2attributes_count1
attribute_infoattributesattributes_count

字段层级访问标志(access_flags):它与类中的access_flags项目是非常类似的,都是一个u2的类型数据,用于表示字段的各种修饰符;

字段访问标志
标志名称标志值含义
ACC_PUBLIC0x0001字段是否public
ACC_PRIVATE0x0002字段是否private
ACC_PROTECTED0x0004字段是否protected
ACC_STATIC0x0008字段是否static
ACC_FINAL0x0010字段是否final
ACC_VOLATILE0x0040字段是否volatile
ACC_TRANSIENT0x0080字段是否transient
ACC_SYNTHETIC0x1000字段是否由编译器自动产生
ACC_ENUM0x4000字段是否enum

简单名(name_index)和描述符(descriptor_index):跟随着access_flags的两个索引值, 它们都是对常量池的引用; 分别代表着字段的简单名称和字段的描述符. 简单名称是指没有类型和参数修饰的方法或者字段名称;描述符的作用是用来描述字段的数据类型或者方法参数列表的类型和返回值.

描述符标识字符含义
标识字符含义标识字符含义
BbyteJlong
CcharSshort
DdoubleZboolean
FfloatVvoid
IintL引用类型
[数组

分析StringTest.class文件: 可以看到field_count是0x0005,说明此类有五个字段属性; 紧接着第一个字段的access_flags为0x0000,说明此字段没有任何修饰符;接着是name_index为0x0014=20指向常量池索引为20的常量,查看常量池索引为20的常量是一个CONSTANT_Utf8_info类型的常量,值为I,说明此字段名称为I; 接着是descriptor_index为0x0015=21指向常量池索引为21的常量,查看常量池此常量是一个CONSTANT_Utf8_info类型的常量, 值为Ljava/lang/Integer,说明此字段的类型是引用类型的Integer,所以可以推断出第一个字段的整体是: Integer I;紧跟着是attributes_count为0x0000,表示没有属性数量;

方法表集合

methods_count(方法计数):一个u2类型的数据,用于表示类有多少个方法;

methods(方法表集合):方法表集合,用于描述类中所有的方法;

方法表结构
类型名称数量类型名称数量
u2access_flags1u2attributes_count1
u2name_index1attribute_infoattributesattributes_count
u2descriptor_indwx1

方法层级访问标识(access_flags);attributes_count:它与类中的access_flags项目是非常类似的,都是一个u2的类型数据,用于表示方法的各种修饰符;

方法访问标识
标识名称标识值含义
ACC_PUBLIC0x0001是否public
ACC_PRIVATE0x0002是否为private
ACC_PROTECTED0x0004是否protected
ACC_FINAL0x0010是否final
ACC_SYNCHRONIZED0x0020是否synchronized
ACC_BRIDGE0x0040是否是有编译器产生的桥接方法
ACC_VARARGS0x0080是否接受不定参数
ACC_NATIVE0x0100是否为native
ACC_ABSTRACT0x0400是否为abstract
ACC_STRICTFP0x0800是否为strictfp
ACC_SYNTHETIC0x1000是否有编译器自动产生的
ACC_STATIC0x0008是否static

简单名(name_index):与字段表中类似的去分析即可;

描述符(descriptor_index):描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号之内.例如java.lang.String.toString()方法描述为()Ljava/lang/String;方法int indexOf(char[]source,int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述为([CII[CIII)I.

属性表集合

在Class文件中, 字段表,方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息;与Class文件中其他的数据项目要求严格的瞬息,长度和内容不同, 属性表集合的限制稍微宽松一些,不在要求各个属性表具有严格顺序, 并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息;

虚拟机规范预定义的属性
属性名使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表final关键字定义的常量值
Deprecated类,方法表,字段表被声明为deprecated的方法和字段
Exception方法表方法抛出的异常
EnclosingMethod类文件仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法;
InnerClass类文件内部类列表
LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量描述
StackMapTableCode属性JDK1.6中新增的属性,供新的类型检查验证器检查和处理目标方法的局部变量和操作数栈所需的类型是否匹配
Signature类,方法表,字段表用于支持泛型情况下的方法签名,在Java语言中, 任何类,接口,初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,则Signature属性会为它记录泛型签名信息.由于Java的泛型采用擦除法实现, 在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的信息
SourceFile类文件记录源文件名称
SourceDebugExtension类文件
Synthetic类,方法表,字段表表示方法或字段为编译器自动生成的
LocalVariableTypeTable它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotions类,方法表, 字段表为动态注解提供支持.,指明哪些注解是运行时可见的
RuntimeInvisibleAnnotation类,方法表, 字段表用于指明哪些注解是运行时不可见的
RuntimeVisableParameterAnnotations方法表作用对象为方法参数, 用于指明哪些注解是运行时可见的
RuntimeInVisableParameterAnnotations方法表作用对象为方法参数, 用于指明哪些注解是运行时不可见的
AnnotationDefault方法表用于记录注解类元素的默认值
BootstrapMethods类文件用于保存invokedynamic指令用用的引导方法限定符

符合规则的属性表应该满足以下结构, 才能够被JVM识别, 否则在运行时虚拟机会忽略掉不认识的属性;

属性表基本结构
类型名称数量
u2attribute_name_index1
u4attribute_length1
u1infoattribute_length

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值