有大牛带学 java, 那就上路吧!
上手推荐学此篇, 啃下来. 记录备复习.
一, 前言
随着我们学习的不断深入, 我相信读者对 class 文件很感兴趣, class 文件是用户编写程序与虚拟机之前的桥梁, 程序通过编译形成 class 文件, class 文件之后会载入虚拟机, 被虚拟机执行, 下面我么来一起揭开 class 文件的神秘面纱.
二, 什么是 class 文件
class 文件是二进制文件, 通常是以. class 文件结尾的文件, 它是以 8 位字节为基础单位的二进制流, 各个数据项紧密排列在 class 文件中, 数据项的基本类型为 u1,u2,u4,u8, 分别表示一个字节, 两个字节, 四个字节, 八个字节的无符号数.
三, class 文件数据结构
其实对于 class 文件而言, 总体的数据结构看上去很规整, 具体的结构如下图所示
下面我们将用一个例子详细讲解 class 文件的各个部分.
四, 示例
下面我们将从这个文件的内容入手, 慢慢分析 class 文件各个部分.
五, class 内容详解
5.1 magic
class 文件的最开始 4 个字节为 magic(魔数), 用来确定该 class 文件能够被虚拟机接受. 而在我们的 class 文件中, 我们可以看到最开始 4 个字节是 CAFEBABE. 所有的 class 文件的开始 4 个字节都是 CAFEBABE.
5.2 minor_version && major_version
主次版本号, 会随着 Java 技术的发展而变化, 表示虚拟机能够处理的版本号. 在 magic 之后的 minor_version 和 major_version 分别是 0 和 52(52 = 3 * 16 + 4).
5.3 constant_pool_count && constant_pool
常量池中常量表的数量和常量表, 常量池中的每一项是常量表, 具体的常量表包含类和接口相关的常量, 存了很多字面量和符号引用. 字面量主要包括了文本字符串和 final 常量. 符号引用包括: 1. 类和接口的全限定名 2. 字段的名称和描述符 3. 方法的名称和描述符.
常量池中的项目包含如下类型:
从上面的图中我们可以知道, 常量池中常量项 (常量项都对应一个表) 为 23(23 = 1 * 16 + 7), 值得注意的是常量项的索引值从 1 开始, 到 22, 总共 22 项, 索引值为 0 的项预留出来, 暂时还未使用. 紧接着就是常量项, 每个常量项的第一个字节 u1 表示标志 (tag), 标志(tag) 表示是什么类型的项目, 标志的值为上表给出的值, 如标志为 1(tag = 1), 表示 CONSTANT_Utf8_info 项目. 上图中的第一个常量项为的标志 tag 的值为 10(10 = 0 * 16 + A), 为 CONSTANT_Methodreef_info 表, 表示类中方法的符号引用. 其中 CONSTANT_Methodref_info 表的结构如下
接着, 在 tag 后面是 u2 类型的 index 项目, 为 4(4 = 0 * 16 + 4), 表示指向常量池的第四项, 由描述可知, 第四项应该是 CONSTANT_Class_info 项, 接着, 又是 u2 类型的 index 项目, 为 18(18 = 1 * 16 + 2), 表示指向常量池的第 18 项, 由表的描述可知, 第十八项应该是 CONSTANT_NameAndType_info 项, 正确性我们之后进行验证. 第一个常量项 CONSTANT_Methodref_info 就完了.
紧接着第一个常量项是第二个常量项, tag 为 9(0 * 16 + 9), 表示 CONSTANT_Fieldref_info 表, 表示字段的符号引用. CONSTANT_Field_info 的表结构如下
接着, 在 tag 后面的是 u2 类型的 index 项目, 为 3(0 * 16 + 3), 表示指向常量池的第三项, 应该为 CONSTANT_Class_info 项, 紧接着是 index 项目, 为 19(1 * 16 + 3), 表示指向常量池的第 19 项, 应该是 CONSTANT_NameAndType_info 项.
接着, 是第三项常量, tag 为 7(0 * 16 + 7), 表示 CONSTANT_Class_info 表, 其中, 其表结构如下
接着 tag 的为类型为 u2 的 index, 为 20(1 * 16 + 4), 表示指向常量池的第二十项, 表示全限定名.
接着第四项常量, tag 为 7(0 * 16 + 7), 表示 CONSTANT_Class_info 表, 表结构已经介绍了, 接着是 u2 的 index, 为 21(1 * 16 + 5), 表示指向全限定名.
接着第五项常量, tag 为 7,u2 的类型的 index 为 22(1 * 16 + 6), 表示指向全限定名.
同理, 按照这样的方法进行分析, 最后给出一个总的常量池表如下.
说明:# 表示常量项的索引, Utf8 表中存放的是具体的字符串. 如 #6 中存放的就是字符串 name,#10 中存放的就是字符串 Code, 关于表示的具体含义, 我们稍后会进行解释.
除去我们之前介绍的常量表结构, 常量池中其他常量表的结构分别如下:
说明: 描述符分为字段描述符和方法描述符, 字段描述符用来描述字段的数据类型, 方法的描述符用来描述方法的参数列表 (包括数量, 类型, 顺序) 和返回值. 基本类型和对象的描述符如下:
说明: 上表中并没有指出出现数组了如何描述, 每一个维度使用一个前置的 "[" 来描述, 如 int[]描述为 [I,String[] 描述为 [Ljava/lang/String;long 类型是使用字符 J 进行标识, 对象类型是使用 L 字符进行标识. 如 String 类型描述为 Ljava/lang/String;short 类型描述为 S, 对于方法描述符而言, 按照先参数列表, 后返回值进行描述, 参数列表按照参数顺序放在小括号 "()" 内部, 如 void inc(int i) 描述为 (I)V;int getName() 描述为 ()I;void setName(String name) 描述为(Ljava/lang/String)V; 方法的描述符与方法名称是分开进行的, 方法描述中并没有包含方法名.
5.4 access_flags
常量池后的两个字节, 用于识别类或接口层次的访问信息, 如, 这个 class 是类或者是接口, 是否为 public,abstract,final 等等. 具体的标志含义如下:
说明: 其中 ACC_INTERFACE 与 ACC_FINAL 不能同时存在.
从之前的字节码中可以知道, access_flags 为 0x0021(0x0021 = 0x0020|0x0001), 即为 public, 并且允许使用 invokespecial 字节码.
5.5 this_class
接着 access_flags 后面的 u2 类型的 this_class, 表示对常量池的索引, 该索引项为 CONSTANT_Class_info 类型, 从前面我们知道 this_class 为 0x0003, 表示对常量池第三项的索引, 第三项我们知道确实是 CONSTANT_Class_info 类型, 而第三项所表示的内容为 Test, 即表示当前类.
5.6 super_class
接着 this_class 后面的是 u2 类型的 super_class, 表示对常量池的索引, 从前面我们知道 super_class 为 0x0004, 表示对常量池第四项的索引, 第四项我们知道是 CONSTANT_Class_info 类型, 而第四项所表示的内容为 java/lang/Object, 表示 Test 的父类为 Object 类.
5.7 interfaces_count && interfaces
接着 super_class 后面的 u2 类型表示接口数量, 此接口数量为该类直接实现或者由接口所扩展接口的数量. 从前面我们可以知道, interfaces_count 为 0x0001, 表示接口数量为 1, 从程序中我们也可以知道确实是只实现了 Cloneable 接口.
接着就是类型为 u2 的 interfaces, 表示对常量池的索引, 值为 0x0005, 表示对第五项的索引, 第五项为 CONSTANT_Class_info 类型, 所表示的内容为 java/lang/Cloneable, 从源程序我们可以进行验证.
5.8 fields_count && fields
接着 interfaces 后面的是类型为 u2 的 fields_count(包括类变量和实例变量, 不包括局部变量), 值为 0x0001, 为 1, 从源程序我们知道只声明了一个实例变量 name, 所以为 1. 接着 fields_count 的是类型为 fields_info 表, field_info 表的具体结构如下
接着 fields_count 后的是 field_info 表, 首先是 u2 类型的 access_flags,access_flags 的具体含义如下表所示
说明: public,private,protected 只能会有一个有效. final,volatile 只能有一个有效.
我们可以知道 access_flags 为 0x0002, 表示为 private, 紧接着是类型为 u2 的 name_index, 值为 0x0006, 表示对常量池第六项的索引, 常量池第六项为 Class_Utf8_info 类型, 内容为 name, 则表示了字段的名称. 接着是类型为 u2 的 descriptor_index, 值为 0x0007, 表示对常量池第七项的索引, 常量池第七项为 Class_Utf8_info 类型, 内容为 Ljava/lang/String, 紧接着是类型为 u2 的 attributes_count, 为 0x0000, 表示 field_info 表没有嵌套 attribute_info 表.
最后的 field_info 表结构如下:
5.9 methods_count && methods
fields 后面的是类型为 u2 的 methods_count,methods_count 的计数只包括在该类或接口中显示定义的方法, 不包括从超类或父接口继承来的方法, 我们可以知道 methods_count 的值为 0x0004, 表示有四个方法, 从源程序我们也可以进行验证. 紧接着 methods_count 的是 method_info 表, method_info 表的具体结构如下(与 field_info 完全相同)
而对于 access_flags 标志种类如下
method_count 为 4 表示接下来有 4 个 method_info 表.
首先是第一个 method_info 表, u2 类型的 access_flags, 为 0x0001, 表示 public, 接着是类型为 u2 的 name_index, 为 0x0008, 表示对常量池第八项的索引, 第八项为 Class_Utf8_info 类型, 内容为 < init>, 表示实例初始化方法, 由编译器产生; 接着是类型为 u2 的 descriptor_index, 为 0x0009, 表示对常量池第九项的索引, 第九项为 Class_Utf8_info 类型, 内容为()V, 表示参数为空, 返回值为 void, 接着是类型为 u2 的 attributes_count, 为 0x0001, 表示有一个属性表; 接着是 attribute_info 表, attribute_info 表的结构如下:
接着 attributes_count 的是类型为 u2 的 attribute_name_index, 为 0x000A, 指向常量池第十项索引, 第十项类型为 Class_Utf8_info 类型, 内容为 Code,Code 属性表示属性的具体类别; 接着是类型为 u4 的 attribute_length, 为 0x00000021, 表示属性长度为 33(2 * 16 + 1), 接着就是具体每个属性的 info 信息, 对于 Code 属性而言, 其结构如下
接着 attribute_length 的是类型为 u2 的 max_stack, 为 0x0001, 表示操作数栈的最大深度, 接着 max_stack 的是类型为 u2 的 max_locals, 为 0x0001, 表示局部变量所需的存储空间大小为 1, 局部变量表的单位为 slot,(byte,char,float,int,short,boolean 等不超过 32 为的数据类型只占据一个 slot,double,long64 为数据类型需要两个 slot), 局部变量表可以存放方法参数(实例方法的 this 引用), 显式处理器的参数 catch 中所定义的异常, 方法体中定义的局部变量. 接着 max_locals 的是类型为 u4 的 code_length, 为 0x00000005, 为 5, 表示 code 代码的长度为 5, 接着 code 的是类型为 u2 的 exception_table_length, 为 0x0000, 表示不存在异常表, 接着是类型为 u2 的 attributes_count(exception_table_length 为 0), 为 0x0001, 为 1, 表示属性数量为 1, 表示有一个属性表, 接着就是 attribute_info 表, 类型为 u2 的 attribute_name_index, 为 0x000B, 表示对常量池第 11 项索引, 第 11 项类型为 Class_Utf8_info, 内容为 LineNumberTable, 表示具体的属性, LineNumberTable 的具体结构如下图所示
接着 attribute_name_index 的是类型为 u4 的 attribute_length, 为 0x0000000A, 长度为 10, 表示属性长度为 10, 接着 attribute_length 的是类型为 u2 的 line_number_table_length, 为 0x0002, 为 2, 表示有两个 line_number_info 表,
line_number_info 表的具体结构如下:
首先是第一个 line_number_info 表, 类型为 u2 的 start_pc, 为 0x0000, 为 0; 接着是类型为 u2 的 line_number, 为 0x0003, 为 3. 第二个 line_number_info 表, 类型为 u2 的 start_pc, 为 0x0004, 为 4, 接着是 line_number, 为 0x0005, 为 5;
至此, 第一个 method_info 表就已经分析完了, 第一个 method_info 表的包含结构如下图所示.
第二个, 第三个, 第四个 Method_info 都可以按照第一个 Method_info 表的方法进行类推. 最后的 4 个表的说明如下
除了上面介绍的属性表之外, 还有其他的属性表, 下面进行介绍.
5.10 attributes_count && attributes
接在 methods 后面的是 attributes_count,attributes_count 为 0x0001, 表示有一个 attribute_info 表, 接着 attribute_count 后面的是 attribute_name_index, 为 0x0010, 表示指向常量池第 16 项的索引, 第 16 项类型为 Class_Utf8_info 类型, 内容为 SourceFile, 表示属性为 SourceFile,SourceFile 属性的具体结构如下
可以看到 attributes_count 为 0x0001, 为 1, 表示有一个属性表, 紧接着, attribute_name_index 为 0x0010, 为 16, 对应常量池第十六项, 类型为 Class_Utf8_info, 内容为 SourceFile, 类型为 u4 的 attribute_length, 为 0x00000002, 值为 2, 紧接着是类型为 u2 的 sourcefile_index, 为 0x0011, 为 17.
至此, 整个 class 文件都已经解析完成了, 其实经过分析, 我们发现其实分析 class 文件并不困难, 都有固定的格式.
六, 特殊字符串
常量池容纳的符号引用包括三种特殊的字符串: 全限定名, 简单名称, 描述符. 全限定名为类或接口的全限定名, 如 java.lang.Object 对象的全限定名为 java/lang/Object, 用 / 代替. 即可. 简单名称为字段名或方法名的简单名称, 如 Object 对象的 toString()方法的简单名称为 toString, 描述符我们在之前已经介绍过了.
七, 指令介绍
7.1 方法调用指令:
1. invokevirtual, 用于调用对象的实例方法, 根据对象的实际类型进行分派.
2. invokeinterface, 用于调用接口方法, 在运行时搜索一个实现了该接口方法的对象, 找出合适的方法进行调用.
3. invokespecial, 用于调用需要特殊处理的实例方法, 包括实例初始化方法, 私有方法, 父类方法.
4. invokestatic, 用于调用类方法, static 方法.
5. invokedynamic, 用于在运行时动态解析出调用点限定符所引用的方法, 并执行该方法.
7.2 返回指令
ireturn(boolean,byte,char,short,int),lreturn,freturn,dreturn,areturn(返回为对象引用类型),return(返回为 void)
7.3 同步指令
虚拟机支持方法级的同步和方法内部一段指令序列的同步, 都使用管程 (Monitor) 来支持. 方法级 (synchronized 修饰) 同步时隐式的, 无需通过字节码指令来控制, 方法调用时检查 ACC_SYNCHRONIZED 标志. 方法内部的 synchronized 语句块使用 monitorenter,monitorexit 指令来确保同步.
七, 总结
class 文件看似很复杂, 其实经过分析我们发现 class 文件并不难, 通过分析 class 文件, 我们知道了源程序经过编译器编译之后如何组织在 class 文件中, 进而为虚拟机执行程序提供搭起了桥梁. 也相信经过分析, 读者也能够分析 class 文件了, 那么我们的目的也就达到了, 谢谢各位园友的观看~
转载自 [JVM] JVM 系列之 Class 文件(三) https://www.cnblogs.com/leesf456/p/5263764.html
作者: https://www.cnblogs.com/leesf456/ 掌控之中, 才会成功; 掌控之外, 注定失败.
来源: http://www.bubuko.com/infodetail-3212298.html