Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件之中,中间没有添加任何分隔符,当遇到需要占用8位字节以上的空间数据时,则会按照高位在前的方式分割成若干个8位字节进行存储。根据Java虚拟机规范的规定,Class只有两种数据类型:无符号数和表。无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节、8个字节是无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值;表是由多个无符号数和其他表作为数据项构成的复合数据结构,所有的表都习惯性的以“_info”结尾。下图为Class文件格式:
每一个Class文件的头4个字节称为魔数,用于确定这个文件是否为一个能被虚拟机接受度Class文件。紧接着魔数的4个字节存储的是Class文件的版本号,Java的版本号是从45开始的,从JDK1.1以后每个JDK大版本发布主版本号向上加1,现在最新版的JDK1.7对应的是51.0,高版本的JDK向下兼容以前版本的Class文件。
常量
紧跟在版本号后面的是常量池,因为常量池数量是不固定的所以常量池的入口需要放置一个u2的数据作为常量池容量计数器,这个计数器从1开始,0表示不引用任何一个常量池。常量池之中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量接近于Java语言层面的常量概念;而符号引用则属于编译原理方面的概念包括三类常量(类和接口的全限定名、字段的名称和描述符、方法的名称和描述符)。常量池中的每一项常量都是一个表,共有11种各不相同的表结构数据,这11种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志物(tag,取值伟1至12,缺少标志为2的数据类型),代表当前这个常量属于那种常量类型,11种常量类型所代表的具体含义如下图:
各种常量的数据结构如下图:
访问标志
在常量池结束之后,紧接着的2个字节访问标志,这个标志用于识别类或接口层次的访问信息,详情如图:
类索引、父类索引与接口索引集合
类索引和父类索引都是 一个u2类型的数据,而接口索引集合石一组u2类型的数据集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于Java语音不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0.接口索引集合就是用来描述这个类实现了哪些借口哦,这些被实现的接口将按implenments语句后的接口顺序从左到右排列在接口的索引集合中。
类索引、父类索引用分别用一个u2类型的索引值表示,他们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。对于接口索引集合,入口的第一项是一个u2类型的数据为计数器(interfaces_count),表示索引表的容量。如果该类没有实现任何接口,那么该值为0,后面接口的索引表不再占用任何字节。
字段表集合
字段表(field_info)用于描述接口或类中声明的变量,包含了类和实例级变量但不包括方法内部声明的变量。字段描述符用标志位来表示,字段名称和数据类型引用常量池中的常量来表示。下图为字段表结构:
下图访问控制符(access_flags):
很明显,在实际情况中,public、private、protected三个只能选一个,final、volatile不能同时选择,接口之中的字段必须与public、static、final标志。
跟随access_flags标志的是两项索引值,他们都是对常量池的引用,分别代表字段的简单名称以及字段和方法的描述符。描述符的作用是用来描述字段的数据类型、方法的参数列表和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,下图为描述符含义图解:
字段表包含的固定数据项到descriptor_index就结束了,不过在之后跟随者一个属性表集合用于存储一些二外的信息,字段都可以在属性表中描述0至多项额外的信息。字段表中不会列出从超类或借口中继承而来的字段,但有可能列出Java代码中不存在的字段,如内部类为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
方法表集合
方法表结构和字段表基本一致,以为volatile和transient关键字不能修饰方法,所以方法表的访问性没有这两项,与之相对的增加了synchronized、native、strictfp、abstract等关键词,详情看下图:
方法的字节码指令存在方法属性表集合中一个名为“Code”的属性里面。与字段表对应的,如果父类方法在子类中没有被重写(Override),方法表集合就不会出现来自父类的方法信息,同样可能会出现变压器自动添加的方法,最典型是类构造器“<clinit>”和实例构造器“<init>”
属性表集合
属性表(attribute_info)用来在Class文件、字段、方法表中描述某些场景专有的信息。Java虚拟机规范第二版中预定义了9项属性,具体如下图:
对于每个属性,他们的名称需要从常量池中引用一个CONSTANY_Utf8_info类型的常量来表示,而属性值的结构则完全自定义的只需要说明属性所占用的位数长度即可,详情看下图:
Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,只有存在方法体的方法才会有此属性,Code属性结构如图:
本文转自 古道卿 51CTO博客,原文链接:http://blog.51cto.com/gudaoqing/1316554