Class类文件结构

# 平台无关性

Java程序
Java程序
Groovy程序
Groovy程序
JRuby程序
JRuby程序
Javac编译器
Javac编译器
.class(字节码文件)
.class(字节码文件)
JVM虚拟机
JVM虚拟机
groovyc编译器
groovyc编译器
Jrubyc编译器
Jrubyc编译器
Viewer does not support full SVG 1.1

# 魔数和Class文件版本

# 魔数

Class文件的前4个字节被称为MagicNumber,唯一作用是确定这个文件是否是一个能被虚拟机接受的class文件。使用魔数而不是使用扩展名的基于安全方面的考虑,扩展名可以随意修改。 Class文件的魔数值为0xCAFEBABE.

# 版本号

第5和第6个字节是次版本号,第7和第8个字节是主版本号。Java虚拟机规范规定虚拟机必须拒绝执行超过其版本号的Class文件。

# 常量池

  紧接着主次版本号出现的就是常量池入口,常量池通常是占用Class文件空间最大的数据项目之一,Class文件中第一个出现的表类型数据项目。 由于常量池中的常量的数量不是固定的,所以常量池入口处放置一项u2类型的数据,代表常量池容量的计数值(16进制表示,如果是22,表示常量池有21项,因为下标从1开始,索引值 为0保留)
  常量池主要存放两大类常量:字面量和符号引用。字面量接近于Java语言层面的字符串文件,被声明final的常量值,符号引用则是编译原理方面。

# 符号引用

符号引用包含以下

  1. 被模块导出或者开放的包package
  2. 类或者接口的全限定名
  3. 字符的名称和描述符
  4. 方法的句柄和方法类型
  5. 动态调用点和动态常量

  Java程序在Javac进行编译时,不会像C或者C++那样连接步骤,而是在虚拟机加载Class文件时候进行动态链接,也就是在Class文件中不会保存各个方法、字段最终在内存中的布局信息, 这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址。

# 访问标志

常量池结束后,紧接着两个字节代表访问标志access_flag。这个标志用于识别一些类或者接口层次的访问信息。

# 类索引、父类索引和接口索引集合

类索引this_class和父类索引super_class都是u2类型的数据,接口索引集合interfaces是一组u2类型的数据的集合,class文件虹由这三项数据来确定该类型的继承关系。 类索引用于确定这个类的全限定名,父类索引用于这个类的父类的全限定名,由于Java语言单继承性,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类, 因此除了java.lang.Object外,所有的Java类的父类索引都不为0.

# 字段表集合

字段表field_info用于描述接口或者类中声明的变量。Java语言中的字段包含类级别变量和实例级别变量,但是不包含方法内部声明的局部变量。字段可以包括的修饰符

  1. 作用域public、private...
  2. 实例变量或者类变量static
  3. 可变性final
  4. 并发可变性,是否强制从主存中读取volatile
  5. 是否可序列化transient
  6. 字段数据类型(基本类型、对象、数组)
  7. 字段名称

# 方法表集合

方发表结构如同字段表,首先是方发表个数,然后是每个方法的访问表示,方法名的index,方法的描述符,方法的参数个数,方法参数名的index,如果父类方法没有在子类中重写,方发表集合中就不会出现来自父类的方法 信息。编译器有可能也会自动添加方法,最常见的就是类构造器<clinit>()和实例构造器<init>()

# 属性表集合

属性表attribute_info用来描述某些场景专有的信息,Class文件、字段表、方发表都可以携带自己的属性表集合。