Class文件是JVM虚拟机的基石,要更好的掌握JVM需要深入理解CLass文件结构@mikechen
Class文件结构
Class 文件是一组以 8 位字节为基础单位的二进制流,各数据项目严格按照顺序紧凑地排列在 class 文件中,class文件的整体结构如下图所示:
上面的表class 文件可以划分为如下七个部分:
- 魔数与class文件版本
- 常 量 池
- 访问标志
- 类索引、父类索引、接口索引
- 字段表集合
- 方法表集合
- 属性表集合
下面我们针对以上class文件一一图文详解。
魔数 Magic Number
魔法字符串,固定为0xCAFEBABE
魔数 Magic Number的作用就是确定这个文件是否是一个能被虚拟机接受的 class 文件,其固定值是:0xCAFEBABE(咖啡宝贝)。
如果一个 class 文件的魔术不是 0xCAFEBABE,那么虚拟机将拒绝运行这个文件,所以主要用来帮助JVM识别是它可运行的文件。
版本号
4 个字节,前 2 个是次版本号 Minor Version,后 2 个主版本号 Major Version
1. 次版本号(minor version):.class 文件的第 5 – 6 个字节,即编译生成该 class 文件的 JDK 次版本号
2. 主版本号(major version):.class 文件的第 7 – 8个字节,即编译生成该 class 文件的 JDK 主版本号
JDK的版本号对照表,这里简单举出几个版本的对照表,如下图:
如果你是用JDK1.8,次版本号就应该是0000,主版本号就是0034。
注意事项:高版本的 JDK 能向下兼容低版本的 .class 文件,但不能运行新版本的.class 文件。
常量池
紧接着版本号之后的是常量池的入口,常量池可以理解为 class 文件之中的资源仓库,它是占用 class 文件空间最大的数据项之一。
常量池是一个集合,它由两部分组成:常量池计数器和常量池
1. 常量池计数器(constant_pool_count)
代表常量池容量计数值(constant_pool_count),这个容量计数是从 1 而不是从 0 开始,第 0 项用于表达“不引用任何一个常量池项目”的含义。
2. 常量池(constant_pool)
常量池中主要存放两大类常量:
- 字面量
- 符号引用
字面量比较接近 Java 语言层面的常量概念,如文本字符串、被声明为 final 的常量值等,而符号引用则属于编译原理方面的概念,主要包括下面几类常量:
- 被模块导出或开放的包(Package)
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
- 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
- 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
每项常量都是一个表,目前 17 种,如下图所示:
访问标志
常量池之后是 u2 类型的访问标志位(access_flags),这个访问标志位用于标识类或者接口层次的访问信息,包括:这个 Class 是类还是接口、是否定义为public类型、是否定义为abstract类型。
具体的标志位以及标志的含义见下表:
类索引、父类索引、接口索引
在访问标记后,会指定该类的类别、父类类别以及实现的接口,格式如下:
这三项数据来确定这个类的继承关系:
- 类索引用于确定这个类的全限定名
- 父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为e。
- 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。
字段表集合
1.字段计数器u2 fields_count
表示字段表数量,后面接着相应数量的字段表。
2.字段表集合field_info
字段表集合(field_info)用于描述接口或类中声明的变量,包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
字段包含待信息有字段的作用域(public、private、protected)、是实例变量还是类变量(static)、可变性(final)等等。这些信息要么有,要么没有,很适合用标志位来表示,而字段叫什么,被定义为什么数据类型,这些都无法固定,只能用常量池中的常量来描述。
字段表结构如下:
access_flags 用来标识字段修饰符(public、static、final、volatile …),name_index 和 descriptor_index 都是对常量池的引用,分别代表字段的简单名称以及字段和方法的描述符。
方法表集合
在字段表之后紧跟着方法表集合,描述接口或类申明的方法信息,有12 种方法访问标志:
属性表集合
方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该class文件的源文件的名称。以及任何带有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试,一般无须深入了解。
此外,字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息。
属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》