Class文件结构

1,319 阅读9分钟

题外话:

本文参考书籍:《深入理解Java虚拟机》有兴趣的同学也可以通过看书了解更多内容。
最近在读这本书,顺便做下知识整理。在了解了class文件结构后可以更好的了解类的加载机制。如果对此内容已经了解的同学可以移步:类的加载机制

如需转载请注明出处,class文件的结构

java语言的一句口号大家应该都很熟悉:"Write once, run anywhere." 那是因为Java虚拟机不与任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。 Java虚拟机也不止支持java一种语言。

图1

图1 Class文件的结构

从上图可以看出多种语言通过编译成*.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文件的表,我们可以看下表的大体结构和具体内容都有什么。

class文件结构

图2 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开始。

图3 常量池结构

如上图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是常量池入口,也是常量池中常量的数量,那么紧接着就是常量池的第一项常量。请看下图。

图4 常量池结构

tag是标志位,区分常量类型;name_index是一个索引,它指向常量池中一个CONSTANT_Utf8_info类型常量,此常量代表了这个类和接口的全限定名。图4中第一个常量的tag为0x07,我们查看图5中的表来确定是哪种常量类型。

图5 常量池项目类型

从图5中可以看出0x07也就是十进制的7代表的类型是CONSTANT_Class_info,然后我们在查询图6中CONSTANT_Class_info的表结构。

图6 CONSTANT_Class_info型的常量结构

这里name_index值为0x0002,指向常量池第二项常量。由图4可以看出,第二项常量的tag是0x01,再查询图5可知,第二个常量是一个CONSTANT_Utf 8_info,我们再来查询CONSTANT_Utf 8_info型常量的结构。见图7。

图7 CONSTANT_Utf8_info型的常量结构


图7中,0x01位tag位,代表CONSTANT_Utf8_info型的常量,0x001D即10进制的29,代表了字符串的长度为29,紧接着后面括号中共29个字节,代表了29个字符,表示了类的全限定名。在图8的右侧大家也可以看一下,类的全限定名被解析出来了,即"org/fenixsoft/clazz/TestClass",有兴趣可以数一下,我数过了是29个字符。[捂脸]

图8 常量池第二项常量结构

以上是给大家举了一些class文件中常量池结构的例子,以及如何分析那一部分对应常量池中的那些内容,下面就不一一举例了。有兴趣的同学可以查看一下官方的文档。
图8是常量池中14种常量项结构总表,在JDK1.7之前只有14种,大家可以大概了解一下,常量池就介绍到这里。

图9 常量池中14种常量项结构总表

三,访问标志

常量池紧接着两个字节代表访问标志(access_flags)。 访问标志:类或接口的层次信息,包括:这个Class是类还是接口;是否是public;是否定义abstract;如果是类的话,是否声明final等。图10表示了访问标志具体表述的所有内容。

图10 访问标志

图11 测试类的访问标志

可以查找图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 测试类类索引、父类索引、实现的接口

分析图12,可以看出图11后access_flags(0x21)后就是类索引、父类索引、类实现的接口信息。从图12可以分析出,this_class(当前类)指向常量池中第一个常量,super_class(父类)指向常量池中第三项,而interfaces_count(实现的接口数)为0,即这个类没有实现任何接口。我们再来看常量池图,图13。

图13 常量池图

刚才在常量池的部分已经讲过常量池的第一个常量是CONSTANT_Class_info代表类或接口的符号引用,第一项常量又指向第二项常量CONSTANT_Utf8_info,即当前类的全限定名,所以这个类的this_class就是"org/fenixsoft/clazz/TestClass",而由图12看出super_class指向常量池中的第三项常量,从图13得出常量池第三项常量(CONSTANT_Class_info)又指向常量池中第四项常量,第四项常量tag为0x01,即CONSTANT_Class_info型,代表了super_class(父类)的全限定名,即"java/lang/Object",这个TestClass类没有继承任何类,所以父类为Object,图中也标记了this_class和super_class代表的字符串。

五,字段表集合

接下来就是字段表集合。
字段表(field_info):用于描述接口或者类中声明的变量。
大家可以想一下Java描述字段可以包含哪些信息?
包括信息有:作用域(public、private、protected)、是否static、是否final、是否volatile、是否transient、字段数据类型(基本类型、对象、数组)、字段名称。

图14 字段表结构

图15 字段访问标志(access_flags)

name_index和descriptor_index都是对常量池的引用。 name_index:字段的简单名称。例如:方法inc()和m字段,简单名称就是:inc和m。 descriptor_index:字段和方法的描述符,用来描述字段的数据类型、方法参数列表(包括数量、类型以及顺序)和返回值,见图16。

图16 描述标志符的含义

六,方法表集合

理解了上面的字段表,方法表的结构和字段表类似。

图17 方法表结构

图18 方法访问标志(access_flags)

class文件结构就先说到这里,如有兴趣的同学可以参考《深入理解Java虚拟机》和《Java虚拟机规范》。

看完此篇以后更容易理解:类的加载机制,如果有兴趣的同学可以继续阅读。

欢迎如有问题欢迎留言。