一.简介
实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java虚拟机不和包括Java在内任何语言绑定,它只与Class文件这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。
Clojure(Lisp 语言的一种方言)、Groovy、Scala 等语言都是运行在 Java 虚拟机之上。下图展示了不同的语言被不同的编译器编译成.class文件最终运行在 Java 虚拟机之上。

可以说.class文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。
二.Class文件结构
根据Java虚拟机规范,类文件由单个ClassFile结构组成:
ClassFile{
u4 magic; //Class 文件的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的访问标记
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口
u2 fields_count;//Class 文件的字段属性
field_info fields[fields_count];//一个类会可以有个字段
u2 methods_count;//Class 文件的方法数量
method_info methods[methods_count];//一个类可以有个多个方法
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
}
根据Java虚拟机规范规定,Class文件格式采用一种类似C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号和表
- 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值
- 表是由多个无符号数或者其他表作为数据项构成的复合数据,所有表都习惯地以"_info"结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质就是一张表。
Class文件具体由以下几个构成:
- 魔数
- 常量池
- 访问标志
- 类索引、父类索引、接口索引集合
- 字段表集合
- 方法表集合
- 属性表集合
2.1 魔数
- Class文件的头4个字节,唯一作用是确定文件是否为一个可被虚拟机接受Class文件,固定为“0xCAFEBABE”。
- 第5和第6个字节是次版本号,第7和第8字节是主版本号(0x0034为52,对应JDK版本1.8)Java的版本号是从45开始的,JDK1.1之后的每一个JDK大版本发布主版本号向上加1,高版本的JDK能向下兼容低版本的JDK。
2.2 常量池
上述图中字节码,版本号后面就是常量池,常量池可以理解为class文件资源仓库,它是class文件结构中与其它项目关联最多的数据类型,也是占用class文件空间最大的数据项目之一,也是class文件结构中与其它项目关联最多的数据类型,也是占用class文件空间最大的数据项目之一,也是class文件中第一出现表类型数据项目。
由于常量池中常量的数量不是固定的,所以常量池入口需要放置一项u2类型的数据,代表常量池的容量计数。不过,这里需要注意的是,这个容器计数是从1开始的而不是从0开始,也就是说,常量池中常量的个数是这个容器计数-1。将0空出来的目的是满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义。class文件中只有常量池的容量计数是从1开始的,对于其它集合类型,比如接口索引集合、字段表集合、方法表集合等的容量计数都是从0开始的。
常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近Java语言常量,如文本字符串,final常量等,而符号引用则属于编译原理方面的概念,包括以下三种:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符号(Descriptor)
- 方法的名称和描述符
不同于C/C++, JVM是在加载Class文件的时候才进行的动态链接,也就是说这些字段和方法符号引用只有在运行期转换后才能获得真正的内存入口地址。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建或运行时解析并翻译到具体的内存地址中。
类型 | 名称 | 解释 | 数量 |
---|---|---|---|
u4 | magic | 1 | |
u2 | minjor_version | 次版本号 | |
majior_version | 主版本号 | ||
constant_pool_count | 常量池常量个数 | ||
cp_info | constant_pool | constant_pool_count-1 | |
access_flags | 访问标记 | ||
this_class | 类索引 | ||
super_class | 父类索引 |
常量池表结构。
表结构又有不同的数据结构。
2.3 访问标志
常量池结束后紧接着的两个字节代表访问标志,用来标识一些类或接口的访问信息,包括:这个Class是类还是接口;是否定义为public;是否定义为abstract;如果是类的话,是否被声明为final等。具体的标志位以及含义如下表:
2.4 当前类索引,父类索引与接口索引集合
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为 0。
接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按implents(如果这个类本身是接口的话则是extends) 后的接口顺序从左到右排列在接口索引集合中。
2.5 字段表集合
u2 fields_count;//Class 文件的字段的个数
field_info fields[fields_count];//一个类会可以有个字段
字段表(field info)用于描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
field info(字段表) 的结构
access_flags: 字段的作用域(public ,private,protected修饰符),是实例变量还是类变量(static修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。
name_index: 对常量池的引用,表示的字段的名称;
descriptor_index: 对常量池的引用,表示字段和方法的描述符;
attributes_count: 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;
attributes[attributes_count]: 存放具体属性具体内容。
字段的 access_flags 的取值
| 标志名称 | 标志值 | 含义 |
|:----????:----????:----????:----????:----????:----????
| ACC_PUBLIC | 0x00 01 | 字段是否为public |
| ACC_PRIVATE | 0x00 02 | 字段是否为private |
| ACC_PROTECTED | 0x00 04 | 字段是否为protected |
| ACC_STATIC | 0x00 08 | 字段是否为static |
| ACC_FINAL | 0x00 10 | 字段是否为final |
| ACC_VOLATILE | 0x00 40 | 字段是否为volatile |
| ACC_TRANSTENT | 0x00 80 | 字段是否为transient |
| ACC_SYNCHETIC | 0x10 00 | 字段是否为由编译器自动产生 |
| ACC_ENUM | 0x40 00 | 字段是否为enum |
2.6 方法表集合
u2 methods_count;//Class 文件的方法的数量
method_info methods[methods_count];//一个类可以有个多个方法
methods_count 表示方法的数量,而 method_info 表示的方法表。
Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
method_info(方法表的) 结构
类文件结构
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 方法是否为public |
ACC_PRIVATE | 0x00 02 | 方法是否为private |
ACC_PROTECTED | 0x00 04 | 方法是否为protected |
ACC_STATIC | 0x00 08 | 方法是否为static |
ACC_FINAL | 0x00 10 | 方法是否为final |
ACC_SYHCHRONRIZED | 0x00 20 | 方法是否为synchronized |
ACC_BRIDGE | 0x00 40 | 方法是否是有编译器产生的方法 |
ACC_VARARGS | 0x00 80 | 方法是否接受参数 |
ACC_NATIVE | 0x01 00 | 方法是否为native |
ACC_ABSTRACT | 0x04 00 | 方法是否为abstract |
ACC_STRICTFP | 0x08 00 | 方法是否为strictfp |
ACC_SYNTHETIC | 0x10 00 | 方法是否是有编译器自动产生的 |
方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为"Code"的属性里面,属性表作为calss文件格式中最具扩展的一种数据项目.
2.7 属性表集合
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合