Class类文件的结构
魔数与Class文件的版本
每个Class文件的头4个字节被称为魔数,它的唯一作用是确定这个文件是否是一个能被虚拟机接受的Class文件。使用魔数来标识而不是扩展名的原因是拓展名可以被随意更改,Class文件的魔数值为0xCAFEBABE。
紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6和字节是此版本号,第7和第8个字节存储的是主版本号。
常量池
在主、次版本号之后的是常量池入口,常量池可以比喻为Class文件里的资源仓库,它是Class文件中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一,另外它还是在Class文件中第一个出现的表类型数据项目。
常量池的入口需要放置一项U2类型的数据,代表常量池容量计数值。需要注意的是这个容量计数是从1开始的(当为5时,代表常量池中有4项常量)。将第0项常量空出来的目的在于如果后面某些指向常量池得到索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。
常量池主要存放两大类常量:字面量和符号引用。字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。符号引用则属于编译原理方面的概念,主要包括下面几类常量:
- 被模块导出或者开放的包
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 方法句柄和方法类型
- 动态调用点和动态常量
常量池中每一项常量都是一个表,截止到JDK13,常量表中分别有17种不同类型的常量。这17类表有一个共同的特点,表结构起始的第一位是一个U1类型的标志位(tag),代表着当前常量属于哪种常量类型。
17种数据类型的结构总表
访问标志
常量池结束后,紧接着的2个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息。
比如一个普通的java类,使用了public关键字修饰,用JDK1.2之后的编译器进行编译,那么它的访问标志值应为 0x0001 | 0x0020 = 0x0021。
类索引、父类索引与接口索引集合
类索引和父类索引都是一个u2类型的数据,接口索引集合是一组u2类型的数据的集合,Class文件中由这三项数据确定该类型的继承关系
- 类的索引来确定这个类的全限定名;
- 父类索引用于确定这个类的父类的全限定名(因为Java中除了Object类所有的Java类都有父类,因此除了Object类外所有类的父类索引都不为0);
- 接口索引集合用来描述这个类实现了哪些接口。
对于接口索引集合,入口的第一项u2类型的数据为接口计数器,表示索引表的容量。
字段表集合
字段表用于描述接口或者类中声明的变量。
用描述符来描述方法时,按照先参数列表、后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“()”之内。如方法void inc()的描述符为“()V”,方法java.lang.String toString()的描述符为“()Ljava/lang/String;”,方法int indexOf(char[]source,int sourceOffset,int sourceCount,char[]target,int targetOffset,int targetCount,int fromIndex)的描述符为“([CII[CIII)I”。
字段表集合中不会列出从父类或者父接口中继承而来的方法,但有可能出现原本Java代码中不存在的字段。
方法表集合
方法表的结构如同字段表一样,依次包括访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)。
如果父类方法在子类中没有被重写,方法表中就不会出现来自父类的方法信息,但是有可能出现编译器自动添加的方法。
在Java语言中,要重载一个方法,要求必须有一个与原方法不同的特征签名(指一个方法中各个参数在常量池中的字段符号引用的集合,因为返回值不包含在特征签名中,所以Java语言无法仅仅依靠返回值的不同来对一个方法进行重载),但在Class文件中,特征签名的范围要更大一些,只要描述符不是完全一致的两个方法就可以共存,也就是说如果两个方法有相同的名称和特征签名,但返回值不同,那么也就是可以合法共存于同一个Class文件中。
属性表集合
虚拟机规范预定义的属性
对于每一个属性,它的名称都要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可。
Code属性
Java程序方法体里面的代码经过Javac编译器处理之后,最终变成字节码指令存储在Code属性内。但并非所有的方法表都拥有这个属性,譬如接口或者抽象类中的方法就不存在Code属性。
异常表结构
如果当字节码从第start_pc行到第end_pc行之间(不含第end_pc行)出现了类型为catch_type或者其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的索引),则转到第handler_pc行继续处理。当catch_type的值为0时,代表任意异常情况都需要转到handler_pc处进行处理。
Exceptions属性
Exceptions属性是在方法表中与Code属性平级的一项属性,作用是列举出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。