字节码文件(Class文件)结构解读、理解与分析 您所在的位置:网站首页 class是哪种文件的扩展名 字节码文件(Class文件)结构解读、理解与分析

字节码文件(Class文件)结构解读、理解与分析

2023-09-20 06:35| 来源: 网络整理| 查看: 265

字节码(Class文件) 什么是字节码(Class文件)?

字节码(Byte-code)是一种包含执行程序,由一序列 op 代码/数据对组成的二进制文件,是一种中间码。字节是电脑里的数据量单位。

对于理解JVM和深入理解Java语言, 学习并了解class文件的格式都是必须要掌握的功课。 原因很简单, JVM不会理解我们写的Java源文件, 我们必须把Java源文件编译成class文件, 才能被JVM识别, 对于JVM而言, class文件相当于一个接口, 理解了这个接口, 能帮助我们更好的理解JVM的行为;另一方面, class文件以另一种方式重新描述了我们在源文件中要表达的意思, 理解class文件如何重新描述我们编写的源文件, 对于深入理解Java语言和语法都是很有帮助的。 另外, 不管是什么语言, 只要能编译成class文件, 都能被JVM识别并执行, 所以class文件不仅是跨平台的基础, 也是JVM跨语言的基础, 理解了class文件格式, 对于我们学习基于JVM的其他语言会有很大帮助。 总之, 在整个Java技术体系结构中, class文件处于中间的位置, 对于理解整个体系有着承上启下的作用。 如图所示:

class文件是一种8位字节的二进制流文件, 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得class文件非常紧凑, 体积轻巧, 可以被JVM快速的加载至内存, 并且占据较少的内存空间。 我们的Java源文件, 在被编译之后, 每个类(或者接口)都单独占据一个class文件, 并且类中的所有信息都会在class文件中有相应的描述, 由于class文件很灵活, 它甚至比Java源文件有着更强的描述能力。

 

字节码文件(Class文件)展示

 

class文件中存在以下数据项:

类型名称数量u4magic1u2minor_version1u2major_version1u2constant_pool_count1cp_infoconstant_poolconstant_pool_count - 1u2access_flags1u2this_class1u2super_class1u2interfaces_count1u2interfacesinterfaces_countu2fields_count1field_infofieldsfields_countu2methods_count1method_infomethodsmethods_countu2attribute_count1attribute_infoattributesattributes_count

以下对class文件中的每一项进行详细的解释。↓↓↓↓↓↓

魔数(4个字节 )

每个Class文件的头4个字节被称为魔数(Magic Number) , 值为0xCAFEBABE(咖啡宝贝? ),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。

版本号(4个字节)

紧接着魔数的4个字节存储的是Class文件的版本号: 第5和第6个字节是次版本号(MinorVersion) , 第7和第8个字节是主版本号(Major Version) 。

常量池(N个字节)

紧接着主版本号之后的就是常量池入口。常量池是Class文件中的资源仓库,在接下来的内容中我们会发现很多地方会涉及,如Class Name,Interfaces等。常量池中主要存储2大类常量:字面量和符号引用。字面量如文本字符串,java中声明为final的常量值等等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符。

由于常量池中常量的数量是不固定的, 所以在常量池的入口需要放置一项u2类型的数据, 代表常量池容量计数值(constant_pool_count) 。这个容量计数是从1而不是0开始的, 如图6-3所示, 常量池容量(偏移地址: 0x00000008) 为十六进制数0x0016, 即十进制的22, 这就代表常量池中有21项常量, 索引值范围为1~21。22=21(索引1~21)+1(第0项常量),设计者将第0项常量空出来是有特殊考虑的, 这样做的目的在于, 如果后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义, 可以把索引值设置为0来表示。  

u2类型:class文件中的信息是一项一项排列的, 每项数据都有它的固定长度, 有的占一个字节, 有的占两个字节, 还有的占四个字节或8个字节, 数据项的不同长度分别用u1, u2, u4, u8表示, 分别表示一种数据项在class文件中占据一个字节, 两个字节, 4个字节和8个字节。 可以把u1, u2, u3, u4看做class文件数据项的“类型” 。

举例 帮助理解常量池结构的解读

若字节码如下

图6-3中常量池的第一项常量, 它的标志位(偏移地址: 0x0000000A) 是0x07, 查表6-3的标志列可知这个常量属于CONSTANT_Class_info类型, 此类型的常量代表一个类或者接口的符号引用。 CONSTANT_Class_info的结构比较简单, 如表6-4所示。

tag是标志位, 它用于区分常量类型; name_index是常量池的索引值, 它指向常量池中一个CONSTANT_Utf8_info类型常量, 此常量代表了这个类(或者接口) 的全限定名, 本例中的name_index值(偏移地址: 0x0000000B) 为0x0002, 也就是指向了常量池中的第二项常量。 继续从图6-3中查找第二项常量, 它的标志位(地址: 0x0000000D) 是0x01, 查表6-3可知确实是一个CONSTANT_Utf8_info类型的常量。 CONSTANT_Utf8_info类型的结构如表6-5所示。

这个字符串的length值(偏移地址: 0x0000000E) 为0x001D, 也就是长29个字节, 往后29个字节正好都在1~127的ASCII码范围以内, 内容为“org/fenixsoft/clazz/TestClass”,如下图所示。

仅仅分析了TestClass.class常量池中21个常量中的两个, 还未提到的其余19个常量都可以通过类似的方法逐一计算出来,下面借助计算机软件来帮忙完成。 在JDK的bin目录中, Oracle公司已经为我们准备好一个专门用于分析Class文件字节码的工具: javap。 代码清单6-2中列出了使用javap工具的-verbose参数输出的TestClass.class文件字节码内容

另外,常量池中的17种数据类型的结构总表可以参考 《深入理解Java虚拟机》周志明著 第三版

Access_Flag 访问标志(2个字节)

访问标志(access_flags) , 这个标志用于识别一些类或者接口层次的访问信息, 包括: 这个Class是类还是接口; 是否定义为public类型; 是否定义为abstract类型; 如果是类的话, 是否被声明为final;等等。具体的标志位以及标志的含义见表6-7。access_flags中一共有16个标志位(2个字节)可以使用, 当前只定义了其中9个。

 

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

类索引、 父类索引和接口索引集合都按顺序排列在访问标志之后,类索引用于确定这个类的全限定名, 父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多继承, 所以父类索引只有一个, 除了java.lang.Object之外, 所有的Java类都有父类, 因此除了java.lang.Object外, 所有Java类的父类索引都不为0。 接口索引集合就用来描述这个类实现了哪些接口, 这些被实现的接口将按implements关键字(如果这个Class文件表示的是一个接口, 则应当是extends关键字) 后的接口顺序从左到右排列在接口索引集合中。

类索引(this_class) 和父类索引(super_class) 都是一个u2类型的数据, 而接口索引集合(interfaces) 是一组u2类型的数据的集合, Class文件中由这三项数据来确定该类型的继承关系。类索引、 父类索引和接口索引集合都按顺序排列在访问标志之后, 类索引和父类索引用两个u2类型的索引值表示, 它们各自指向一个类型为CONSTANT_Class_info的类描述符常量, 通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。对于接口索引集合, 入口的第一项u2类型的数据为接口计数器(interfaces_count) , 表示索引表的容量。 如果该类没有实现任何接口, 则该计数器值为0, 后面接口的索引表不再占用任何字节。

字段表集合

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

Java语言中的“字段”(Field) 包括类级变量以及实例级变量, 但不包括在方法内部声明的局部变量。

字段可以包括的修饰符有字段的作用域(public、 private、 protected修饰符) 、 是实例变量还是类变量(static修饰符) 、 可变性(final) 、 并发可见性(volatile修饰符, 是否强制从主内存读写) 、 可否被序列化(transient修饰符) 、 字段数据类型(基本类型、 对象、 数组) 、字段名称。 上述这些信息中, 各个修饰符都是布尔值, 要么有某个修饰符, 要么没有, 很适合使用标志位来表示。 而字段叫做什么名字、 字段被定义为什么数据类型, 这些都是无法固定的, 只能引用常量池中的常量来描述。

  方法表集合

Class文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式, 方法表的结构如同字段表一样, 依次包括访问标志(access_flags) 、 名称索引(name_index) 、 描述符索引(descriptor_index) 、 属性表集合(attributes) 几项, 如表6-11所示。

属性表集合

(先空着)

 

 

 

 

 

参考:

《深入理解Java虚拟机:JVM高级特性与最佳实践》第三版 周志明著 

什么是字节码?https://www.jianshu.com/p/9732fe15b8dd

深入理解Java Class文件格式(一)https://blog.csdn.net/zhangjg_blog/article/details/21486985

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有