Class文件结构(7大内容格式详细解析)

Class文件结构(7大内容格式详细解析)-mikechen

Class文件是JVM虚拟机的基石,要更好的掌握JVM需要深入理解CLass文件结构@mikechen

Class文件结构

Class 文件是一组以 8 位字节为基础单位的二进制流,各数据项目严格按照顺序紧凑地排列在 class 文件中,class文件的整体结构如下图所示:

Class文件结构(7大内容格式详细解析)-mikechen

上面的表class 文件可以划分为如下七个部分:

  • 魔数与class文件版本
  • 常 量 池
  • 访问标志
  • 类索引、父类索引、接口索引
  • 字段表集合
  • 方法表集合
  • 属性表集合

下面我们针对以上class文件一一图文详解。

 

魔数 Magic Number

魔法字符串,固定为0xCAFEBABE

Class文件结构(7大内容格式详细解析)-mikechen

魔数 Magic Number的作用就是确定这个文件是否是一个能被虚拟机接受的 class 文件,其固定值是:0xCAFEBABE(咖啡宝贝)。

如果一个 class 文件的魔术不是 0xCAFEBABE,那么虚拟机将拒绝运行这个文件,所以主要用来帮助JVM识别是它可运行的文件。

 

版本号

Class文件结构(7大内容格式详细解析)-mikechen

4 个字节,前 2 个是次版本号 Minor Version,后 2 个主版本号 Major Version

1. 次版本号(minor version):.class 文件的第 5 – 6 个字节,即编译生成该 class 文件的 JDK 次版本号

2. 主版本号(major version):.class 文件的第 7 – 8个字节,即编译生成该 class 文件的 JDK 主版本号

JDK的版本号对照表,这里简单举出几个版本的对照表,如下图:

Class文件结构(7大内容格式详细解析)-mikechen

如果你是用JDK1.8,次版本号就应该是0000,主版本号就是0034。

注意事项:高版本的 JDK 能向下兼容低版本的 .class 文件,但不能运行新版本的.class 文件。

 常量池

Class文件结构(7大内容格式详细解析)-mikechen

紧接着版本号之后的是常量池的入口,常量池可以理解为 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 种,如下图所示:

Class文件结构(7大内容格式详细解析)-mikechen

 

访问标志

Class文件结构(7大内容格式详细解析)-mikechen

常量池之后是 u2 类型的访问标志位(access_flags),这个访问标志位用于标识类或者接口层次的访问信息,包括:这个 Class 是类还是接口、是否定义为public类型、是否定义为abstract类型。

具体的标志位以及标志的含义见下表:

Class文件结构(7大内容格式详细解析)-mikechen

 

类索引、父类索引、接口索引

在访问标记后,会指定该类的类别、父类类别以及实现的接口,格式如下:

Class文件结构(7大内容格式详细解析)-mikechen

这三项数据来确定这个类的继承关系:

  • 类索引用于确定这个类的全限定名
  • 父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为e。
  • 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。

 

字段表集合

Class文件结构(7大内容格式详细解析)-mikechen

1.字段计数器u2 fields_count

表示字段表数量,后面接着相应数量的字段表。

2.字段表集合field_info

字段表集合(field_info)用于描述接口或类中声明的变量,包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。

字段包含待信息有字段的作用域(public、private、protected)、是实例变量还是类变量(static)、可变性(final)等等。这些信息要么有,要么没有,很适合用标志位来表示,而字段叫什么,被定义为什么数据类型,这些都无法固定,只能用常量池中的常量来描述。

字段表结构如下:

Class文件结构(7大内容格式详细解析)-mikechen

access_flags 用来标识字段修饰符(public、static、final、volatile …),name_index 和 descriptor_index 都是对常量池的引用,分别代表字段的简单名称以及字段和方法的描述符。

 

方法表集合

Class文件结构(7大内容格式详细解析)-mikechen

在字段表之后紧跟着方法表集合,描述接口或类申明的方法信息,有12 种方法访问标志:

Class文件结构(7大内容格式详细解析)-mikechen

属性表集合

Class文件结构(7大内容格式详细解析)-mikechen

方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该class文件的源文件的名称。以及任何带有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试,一般无须深入了解。

此外,字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息。

属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性。

陈睿mikechen

10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

关注「mikechen」公众号,获取更多技术干货!

后台回复面试即可获取《史上最全阿里Java面试题总结》,后台回复架构,即可获取《阿里架构师进阶专题全部合集

评论交流
    说说你的看法