一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
1.概述
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有添加任何的分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据。当遇到需要占用8个字节以上空间的数据项时,则会按照高位在前的方式分隔成多个8位字节进行存储。
Class文件采用一种类似于C语言结构体的伪结构来存储数据,这种结构中只存在两种数据类型:无符号数和表。
- 无符号数: 属于基本数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值
- 表: 由多个无符号数或其他表作为数据项构成的符合数据类型,所以表都习惯以_info结尾。表用于描述有层次关系的复合结构的数据。整个Class文件本质上就是一张表,它由下表所示的数据项构成
2.类文件结构
2.1 魔数
- 每个Class文件的头4个字节成为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。Class文件的魔数值为:0xCAFEBABE。
- 紧接着魔数的4个字节存储的是Class文件的版本号:5、6两个字节是次版本号(Minor Version),7、8两个字节是主版本号(Major Version;
2.2 常量池
-
紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。
-
由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。这个容量计数是从1而不是0开始的,设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况就可以把索引值置为0来表示。Class文件结构中只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量:比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。
符号引用:则属于编译原理方面的概念,包括了下面三类常量:
类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符(Descriptor)
方法的名称和描述符
2.3 访问标志
在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识 别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类 型;是否定义为abstract类型;如果是类的话,是否被声明为final等。
2.4 类索引 父类索引 接口索引
类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串
2.5字段表集合
字段表(field_info)用于描述接口或者类中声明的变量
字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常类似的,都是一个u2的数据类型。
跟随access_flags标志的是两项索引值:name_index和descriptor_index。它们都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。根据描述符规则,基本数据类型以及代表无返回值类型的void类型都用一个大写字母表示,而对象类型则是字符L加对象的全限定名表示
2.6方法表集合
Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式,方法表的结构如 同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索 引(descriptor_index)、属性表集合(attributes)几项。 方法访问标志
2.7属性表集合
与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松了一些,不再要求各个属性表具有严格顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。