Java 文件编译后会生成 .class
二进制文件,这个文件以 8 字节为单元组织。在 Class 文件中定义了一些数据类型,u1,u2,u4,u8 分别代表 1 个字节、2 个字节、4 个字节、8 个字节的无符号数。
Class 文件像一张大表格,以一定的格式记录。
ClassFile
{
magic_number;
minor_version;
major_version;
constant_pool_count;
constant_pool[];
access_flags;
this_class;
super_class;
interfaces_count;
interfaces[];
fields_count;
fields[];
methods_count;
methods[];
attributes_count;
attributes[];
}
Magic Number
每个 Class 文件头 4 个字节称为 Magic Number,用于确认该文件是否能被虚拟机接受。
Class 文件的 Magic Number 是 0xCAFEBABE
.
apt install ghex
Class 版本
接着 Magic Number 后面 4 个字节就是 Class 文件的版本号
- 第 5,6 个字节是次版本号 Minor Version
- 第 7,8 个字节是主版本号 Major Version
Java 版本号从 45 开始,JDK 1.1 后每个 JDK 大版本发布主版本加 1,高版本的 JDK 向下兼容以前的 Class 文件,但不能运行以后的版本。
看到上图里面,5,6 个字节 0x0000,主版本号 0x0033,就是十进制的 51,说明这个 Class 文件可以被 JDK 1.7 或者以上版本虚拟机执行。
常量值 constant_pool
在主版本号后面是常量池入口,所有的变量,方法都会在该常量池有一份引用。因为常量值数量不固定,所以常量值入口会放置 u2 类型数据,表示常量池容量计数 constant_pool_count ,这个容量计数是从 1 开始。
常量池主要存放两大类常量:
- 字面值 Literal
-
符号引用 Symbolic References , 包含
- 类和接口的全限定名 Fully Qualified Name - 字段名和描述符 Descriptor - 方法的名称和描述符
常量池中每一项都是一个表
第一位是 u1 类型标志位,取值 1 到 12,缺少标志为 2 的数据类型,该标志表示当前常量属于哪种类型。
类型 | 标志 | 描述 |
---|---|---|
CONSTANT_Utf8_info | 1 | UTF-8 编码字符串 |
CONSTANT_Integer_info | 3 | 整型字面值 |
CONSTANT_Float_info | 4 | 浮点 |
CONSTANT_Long_info | 5 | 长整型 |
CONSTANT_Double_info | 6 | 双精度浮点 |
CONSTANT_Class_info | 7 | 类或者接口符号引用 |
CONSTANT_String_info | 8 | 字符串类型 |
CONSTANT_Fieldref_info | 9 | 字符的符号引用 |
CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
如果看二进制的 Class 文件比较麻烦,JDK 中提供了了分析 Class 文件字节码的工具:javap
javap -verbose filename.class
可以看到类似如下的内容:
Constant pool:
#1 = Methodref #11.#36 // java/lang/Object."<init>":()V
#2 = Fieldref #37.#38 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #39 // hello
#4 = Methodref #40.#41 // java/io/PrintStream.println:(Ljava/lang/String;)V
访问标志 access flags
常量池结束后接 2 字节代表访问标志 access_flag. 用于识别类或接口层次的访问信息,包括 Class 是类还是接口,是否定义为 public,是否定义为 abstract 类型,类是否被声明为 final .
类索引 this_class 父索引 super_class 接口索引 interfaces
Class 文件由这个三个数据来确定类的继承关系。
- 类索引确定类的全限定名
- 父类索引确定这个父类的全限定名,除 java.lang.Object 外,所有 Java 类的父类索引都不是 0
- 接口索引描述类实现了哪些接口,按照实现的顺序排列
类索引、父类索引和接口索引都按照顺序排列在访问标志后,类索引和父类索引用两个 u2 类型索引值表示,各自指向 CONSTANT_Class_info 的类描述符常量,通过 CONSTANT_Class_info 类型的常量中的索引值来找到定义在 CONSTANT_Utf8_info 类型的常量中的全限定名字符串。
接口索引,入口是 u2 类型数据的接口计数 interfaces_count ,表示索引表容量,如果没有实现任何接口,则计数器为 0。
字段表集合 field info
用于描述接口或者类中声明的变量,包括类级变量或者实例级变量,但不包括方法内部声明变量。
- 字段作用域 public private protected
- 类级变量还是实例变量 static
- 可变性 final
- 并发可见性 volatile,是否强制从主存读写
- 可否序列化 transient
- 字段基本类型,对象,数组
- 字段名称
方法表集合
方法表结构和字段表相同,依次包括了访问标志 access flags, 名称索引 name index, 描述符索引 descriptor index , 属性表集合 attributes
属性表集合 attribute info
与 Class 文件中其他数据项目要求严格的顺序、长度和内容不同,属性表集合限制宽松,不要求属性表具有严格顺序,只要不与已有的属性名重复,任何人实现的编译器都可以向属性中写入自己定义的属性信息,Java 虚拟机在运行时会忽略掉它不认识的属性。