题外话:
本文参考书籍:《深入理解Java虚拟机》有兴趣的同学也可以通过看书了解更多内容。
最近在读这本书,顺便做下知识整理。在了解了class文件结构后可以更好的了解类的加载机制。如果对此内容已经了解的同学可以移步:类的加载机制。
如需转载请注明出处,class文件的结构 。
java语言的一句口号大家应该都很熟悉:"Write once, run anywhere." 那是因为Java虚拟机不与任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。 Java虚拟机也不止支持java一种语言。
从上图可以看出多种语言通过编译成*.class文件都可以在JVM上运行。 下面让我们了解一下class文件的结构。
Class类文件结构
注:(此文以JDK1.4为主线讲解)
首先看一下class文件的基本概念:
class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序节凑的排列在Class文件中,中间没有添加任何分隔符,使得整个Class文件中从存储的内容几乎全部是程序运行时的必要数据,没有任何空隙。当遇到需要占用8位以上空间的数据项时,则会按照高位在前的方式分割为若干个8位字节进行存储。
Class文件结构中只有两种数据类型:1,无符号数。2,表。
1,无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表1、2、4、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值、或者按照UTF-8编码的字符串值。
2,表:由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以“info”结尾。Class文件本质上就是一张表。
下面是一张普通class文件的表,我们可以看下表的大体结构和具体内容都有什么。
图2中红字标注后面会做具体分析。
一,魔数和Class文件的版本
魔数
每个Class文件的前4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否是一个能被虚拟机接受的Class文件。使用魔数而不使用扩展名来验证是出于安全考虑,因为扩展名可以随意改变。java语言魔数就是:CAFEBABE(十六进制数)。此 魔数也用于后面类加载中的验证。
Class文件版本
Class第5、6个字节是次版本号,7、8个字节是主版本号。(主版本号就是我们常说的JDK1.7或1.8,次版本号就是1.7后面的小版本号。)
二,常量池
常量池可以理解为Class文件的资源仓库。 由于常量池中常量的数量是不固定的,所以在常量池入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。常量池索引从1开始。
如上图6-3所示,魔数后是小版本号和大版本号,后面紧跟着就是常量池入口,入口前两个字节表示了常量池中常量的个数。图中都为十六进制数,0x16就是十进制的22,而常量池计数都是从1开始,不是从0开始,所以这里面表示常量池中索引范围是1~21,而非0~21,所以常量池中共有21项常量。
常量池中主要存放两大类常量:1,字面量。2,符号引用。
1,字面量
接近于Java语言层面的常量概念。如:字符串,final常量等。
2,符号引用
属于编译原理方面概念,包括下面三类:
(1)类和接口的全限定名
(2)字段的名称和描述
(3)方法的名称和描述
常量池中每一项常量都是一个表,在JDK1.7之前共有14种结构各不相同的表结构数据,1.7之后又额外增加了3种。
这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位(tag),代表当前这个常量属于哪种常量类型。
下面我们来看下这些表的结构。
上面我们已经说过第九个字节0x16是常量池入口,也是常量池中常量的数量,那么紧接着就是常量池的第一项常量。请看下图。
tag是标志位,区分常量类型;name_index是一个索引,它指向常量池中一个CONSTANT_Utf8_info类型常量,此常量代表了这个类和接口的全限定名。图4中第一个常量的tag为0x07,我们查看图5中的表来确定是哪种常量类型。
图5 常量池项目类型图7中,0x01位tag位,代表CONSTANT_Utf8_info型的常量,0x001D即10进制的29,代表了字符串的长度为29,紧接着后面括号中共29个字节,代表了29个字符,表示了类的全限定名。在图8的右侧大家也可以看一下,类的全限定名被解析出来了,即"org/fenixsoft/clazz/TestClass",有兴趣可以数一下,我数过了是29个字符。[捂脸] 图8 常量池第二项常量结构
图8是常量池中14种常量项结构总表,在JDK1.7之前只有14种,大家可以大概了解一下,常量池就介绍到这里。 图9 常量池中14种常量项结构总表
三,访问标志
常量池紧接着两个字节代表访问标志(access_flags)。 访问标志:类或接口的层次信息,包括:这个Class是类还是接口;是否是public;是否定义abstract;如果是类的话,是否声明final等。图10表示了访问标志具体表述的所有内容。
图10 访问标志可以查找图10中的表测试类访问标志为0x21就是图10中ACC_PUBLIC和ACC_SUPER的结合,可见这个TestClass类就是一个普通的public的java类。
这种方式大家可以联想一下linux的权限码,我感觉很类似,例如:chmod 755 + 文件名,755中三位数字分表表示文件所属用户的权限、文件所属组的权限、其他用户权限。4代表读权限,2代表写权限,1代表执行权限。755就是赋予文件所有者读、写、执行权限(4 + 2 + 1 = 7),赋予文件所有者的组读、执行的权限(4 + 1 =5),赋予其他用户也是读和执行的权限(4 + 1 = 5)。是不是与这个access_flags非常相似,用很少的字符表示多个属性。
四,类索引、父类索引与接口索引集合
访问标志后接的下来就是类索引和父类索引,类索引和父类索引都是u2类型数据,接口索引集合是一组u2类型的数据集合。Class文件中由这三项数据来确定类的继承关系。
(1)类索引,类全限定名。
(2)父类全限定名。
(3)实现的接口。入口第一项是接口计数器,即表示索引表的容量。如果没有实现接口,计数器值为0,后面接口索引表就不占用任何字节。
图12 测试类类索引、父类索引、实现的接口五,字段表集合
接下来就是字段表集合。
字段表(field_info):用于描述接口或者类中声明的变量。
大家可以想一下Java描述字段可以包含哪些信息?
包括信息有:作用域(public、private、protected)、是否static、是否final、是否volatile、是否transient、字段数据类型(基本类型、对象、数组)、字段名称。
六,方法表集合
理解了上面的字段表,方法表的结构和字段表类似。
图17 方法表结构看完此篇以后更容易理解:类的加载机制,如果有兴趣的同学可以继续阅读。
欢迎如有问题欢迎留言。