JVM之Class文件结构
- Class文件的总结构
Class文件总体结构如下:
① 魔数
② Class文件版本
③ 常量池
④ 访问标志
⑤ 类索引、父类索引、接口索引集合
⑥ 字段表集合
⑦ 方法表集合
⑧ 属性表集合 - 魔数-Class文件的标记
① 每一个Class文件开头的4个字节的无符号整数称为魔数
② 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件
③ 魔数值固定为0xCAFEBABE,不会改变
④ 如果一个Class文件不以0xCAFEBABE开头,虚拟机就会报错,通过修改扩展名是没有用的 - Class文件版本号
① 第5,6字节代表编译的副版本号minor_version,第6,7字节代表编译的主版本号major_version
② 共同构成class文件的格式版本号,例如Class文件的主版本号为M,副版本号为m,那么Class文件的格式版本号为M.m
③ 不同版本的Java编译器编译的Class文件对应的版本是不一样的,高版本可以兼容低版本,但低版本不能兼容高版本,即向下兼容原则 - 常量池-存放所有常量
(1)常量池计数器
由于常量池的数量不固定,时长时短,所以需要两个字节表示常量池计数器
常量池计数值从1开始,表示常量池中有多少项常量,constant_pool_count=1表示常量池中有0个常量项
常量池计数值从1开始,把第0项空出来是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目的含义”,这种情况可以用索引0来表示
(2) 常量池表
① constant_pool是一种表结构,以1-constant_pool_count-1为索引,表示后面有多少个常量项
② 常量池主要存放两大类常量:字面量和符号引用
③ 它包含了class文件结构及其子结构中引用的所有字符串常量,类或接口名,字段名和其他常量
(3)字面量和符号引用
说明:
① 全限定名:就是把类的包名的.替换为/就是类的全限定名,通常在最后会加一个;表示权限定名结束
② 简单名称:指没有类型和参数修饰的方法或者字段名称
③ 描述符:用来描述字段的数据类型、方法的参数列表(包括数量,类型以及顺序)和返回值
(4)补充说明
虚拟机在加载Class文件时才会进行动态链接,Class文件不会保存各个方法和字段的最终内存布局信息,因此这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。当虚拟机运行时,需要从常量池中获得对应的符号引用,再在类加载过程中的解析阶段将其替换为直接引用,并翻译到具体的内存地址中
① 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量。只要使用时能无歧义的定位到目标即可,符号引用和虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中
② 直接引用:可以是直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄,直接引用是与虚拟机实现的内存布局相关的。同一符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那说明引用的目标一定在内存中了 - 访问标识
在常量池后,紧跟着访问标记,该标记使用两个字节表示,用于实现一些类或者接口层次的访问信息,包括这个Class是类还是接口,是否定义为public类型,是否定义为abstract类型,如果是类是否被声明为final等,具体标记如下所示:
- 类索引、父类索引和接口索引集合
这三项数据确定这个类的继承关系
① 类索引用于确定这个类的全限定名
② 父类索引用于确定这个类的父类的全限定名,父类索引只有一个,处理java.lang.Object外,所有Java类的父类索引都不为0
③ 接口索引集合用来描述这个类实现了哪些接口,这些被实现的接口将按implement语句后的接口顺序从左到右排序在接口索引集合中 - 字段表集合
(1)fields
① 用于描述接口或类中声明的变量,字段包括类级变量以及实例级变量,不包括局部变量
② 字段叫什么名字,被定义为什么数据类型,都是无法固定的,只能引用常量池中的常量来描述
③ 它指向常量池索引集合,它描述了每个字段的完整信息,比如字段的标识符,访问修饰符,是类变量还是实例变量,是否是常量等
注意事项:
① 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码中不存在的字段,比如在内部类中为了保持对外部类的访问,会自动添加指向外部类实例的字段
② 在Java语言中字段是无法重载的,但是对于字节码来说,如果两个字段的描述符不一致,那字段重名是合法的
(2)字段计数器(fileds_count)
fileds_count的值表示当前class文件fields表的成员个数,使用两个字节表示。fields表中每个成员都是一个field_info结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父类接口继承的字段
(3)fields [](字段表)
① fields表中的每一个成员都必须是一个field_info结构的数据项,用于表示当前类或接口中某个字段的完整描述
② 字段表作为一个表,同样有它自己的结构
③ 字段表访问标识
④ 字段名索引:根据字段名索引的值,查询常量池中的指定索引项
⑤ 描述符索引
⑥ 属性表集合
一个字段可能拥有一些属性,用于存储更多的额外信息,比如初始值,一些注释信息等,属性个数存储在attribute_count中,属性具体内容存放在attribute数组中 - 方法表集合
(1)methods_count(方法计数器)
methods_count的值表示当前class文件methods表的成员个数,使用两个字节表示,methods表中每个成员都是一个methods_info结构
(2)methods:指向常量池索引集合,它完整描述了每个方法的签名
① **在字节码文件中,每一个method_info项都对应着一个类或者接口中的方法信息。**比如方法的访问修饰符,方法的返回值类型以及方法的参数信息等
② 如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来
③ methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法;methods表有可能会出现有编译器自动添加的方法,最典型的便是编译器产生的方法信息,比如类初始化方法()和实例初始化方法()
注意事项:
① Java 中要重载一个方法,除了要与原方法具有相同的简单名称外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,因为返回值不会包含在特征签名中,所以Java语言中无法仅靠返回值的不同来对一个已有方法进行重载。
② 在Class文件中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存,也就是说如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个class文件中
③ 虽然Java语法规范不允许一个类或接口中声明多个方法签名相同的方法,但是字节码文件中却可以,唯一的条件就是这些方法之间的返回值不能相同
④ 方法表结构
⑤ 方法访问表标志
- 属性表集合
属性表集合指的是class文件所携带的辅助学习,比如该class文件的源文件的名称,这类学习通常被用于Java虚拟机的验证和运行以及Java程序的调试。attributes_count的值表示当前class文件属性表的成员个数,属性表中的每一项都是一个attributes_info结构
(1) 属性表的通用格式
(2)属性类型
Java8里面定义了23种属性,这里列举一部分,其余的可以去官网进行查看
(3)code属性表的结构
① LineNumberTable属性表结构
用来描述Java源码行号与字节码行号之间的对应关系;start_pc字节码行号;line_numberJava源代码行号
② LocalVariableTable属性表结构
LocalVariableTable被调试器用于确定方法在执行过程中局部变量的信息
start_pc+length表示这个变量在字节码中的生命周期起始和结束的偏移位置
index是这个变量在局部变量表中的槽位
name为变量名称
Descriptor表示局部变量类型描述