【JVM】详解 Class类文件的结构
目录
魔数与版本号
常量池
访问标志
类索引、父类索引和接口索引集合
字段表
方法表
属性表
Code属性
Exceptions属性
Class文件是以八字节为基础单位的二进制流,任何一个Class文件都对应着唯一的一个类或接口的定义信息。
Class文件格式采用“无符号数”和“表”来记录数据:
- 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
- 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。
Class文件格式如图
魔数与版本号
Class文件的头四个字节为魔数(Magic Number),用来确定Class文件是否能被JVM接受。
紧跟着的4个字节就为版本号,第5和第6个字节是 次版本号(Minor Version),第7和第8个字节是主版本号(MajorVersion)。版本号目的是让对应的JDK运行对应版本的Class文件,高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件。
常量池
常量池为表类型的数据,可以理解为Class文件的资源库。
在版本号之后的就是常量池入口,常量池入口中放置了u2类型的数据,代表常量池容量计数值(constant_pool_count)。
常量池中每一项常量都是一个表。常量池主要存放两大类常量:字面常量(Literal)和符号引用(SymbolicReferences):
字面常量接近Java中的常量概念,如字符串,final修饰的常量值等。
符号引用则属于编译原理方面的概念,主要包括下面几类常量:
- 被模块导出或者开放的包(Package)
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
- 方法句柄和方法类型(Method Handle、Method Type、InvokeDynamic)
- 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
访问标志
常量池之后就是访问表示,用来标记类和接口的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等等。
类索引、父类索引和接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合。这三个数据类型决定了Class文件对应的类的继承关系。
字段表
字段表(field_info)用于描述接口或者类中声明的变量。结构如下图。
字段可以包括的修饰符有:字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。
上述可以用bool类型的标志位来表示,通过放在access_flags字段修饰符在其中设置标志位。
跟随access_flags标志的是两项索引值:name_index和descriptor_index。它们都是对常量池项的引用,分别代表着字段的简单名称以及字段和方法的描述符。
字段表集合中不会列出从父类或者父接口中继承而来的字段,但有可能出现原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,编译器就会自动添加指向外部类实例的字段。
方法表
方法表的结构如同字段表一样,依次包括访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)。
在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名。特征签名是指一个方法中各个参数在常量池中的字段符号引用的集合。但如果两个方法简单名称、特征签名相同,返回值不同,也可以被视作重载的不同方法。
属性表
Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。结构如下图。
Code属性
Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。
try-catch代码块中的异常通过异常表来处理。
Exceptions属性
Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Excepitons),也就是方法描述时在throws关键字后面列举的异常。与异常表中处理的异常不同。