【Class 文件结构】— 常量池
前言
Class 文件是一组以 8 位字节为单位的二进制流,各个数据项目严格按照顺序紧凑地排列在 Class 文件之中。Class 文件中存储数据有两种数据类型:无符号数和表
- 无符号数:Class 文件中基本的数据结构,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数
- 表:多个无符号数或者其他表作为数据项构成的复合数据结构,所有表都习惯的以“_info”结尾。
下面是一段代码
package com.somelogs.bug;
/**
* 描述
*
* @author LBG - 2018/1/11 0011 10:25
*/
public class TestClass {
private int m;
public int inc() {
return m + 1;
}
}
编译后生成 TestClass.class 文件,用 WinHex 打开 class 文件,如下图
Class 文件格式
类型 | 名称 | 数量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count - 1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
魔数
每个 Class 文件前 4 个字节成为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接收的 Class 文件。魔数的值为:0xCAFEBABE,就记着“咖啡宝贝”吧~
版本号
魔数后面的四个字节是 Class 版本号,前两个字节是次版本号(minor version),后两个是主版本号(major version)。上图中次版本号为 0x0000,主版本号为 0x0033,也就是十进制的 51,,说明该 Class 是可以被 JDK 1.7 及以上的虚拟机执行。
常量池
紧接着版本号的就是常量池,常量池可以理解为 Class 文件的资源仓库。
常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
- 字面量:比较接近于 Java 语言层面的敞亮概念,如文本字符串、声明为 final 的常量等
- 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符
常量池容量
常量池的入口存放 u2 类型的数据,表示常量池容量计数值。不过需要注意的是
常量池中常量的数量 = 常量池容量计数值 - 1
比如上图中 0x0016,表示常量池容量值为 22,那么常量的个数就是 21。
常量
常量池中每一项常量都是一个表,总共有 14 中结构不同的表,如下
表 1.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 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 标识方法句柄 |
CONSTANT_MethodType_info | 16 | 标识方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个方法的调用点 |
上面 14 中表有一个共同点,就是表的第一位是一个 u1 类型的的标志位(tag),取值看上面表格中的标志值。
常量项的结构总表
表 1.3
回到上面示例代码 Class 文件,第一个常量的 tag 为 0x0A,十进制是 10,查表 1.2 可知该常量类型是 CONSTANT_Methodref_info。再查表 1.3 可以知道 CONSTANT_Methodref_info 的数据结构,tag 占一个字节,后面两个 index 分别占用 2 个字节。在图中表示为 0x0004:指向常量池中第 4 个常量、0x0012:指向常量池中第 18 个常量。紧接着这个常量的是以 0x09 开头的,查表 1.2 得知是 CONSTANT_Fieldref_info 类型,然后再查表 1.3 看他的数据结构,也是占 5 个字节。然后继续下一个常量...依次可以找出所有常量,如下图
回头看第一个常量的第一个 index:0x0004,指向第四个常量,看上面图第四个常量是 0x07 开头,查表 1.2 可知是 CONSTANT_Class_info 类型,并且 index 为 0x0015,十进制等于 21,也就是指向第 21 个常量。也就是图中最后一个红色方框 0x01,为 CONSTANT_Utf8_info 类型。CONSTANT_Utf8_info 类型的 length 说明了这个 UTF-8 编码的字符串长度是多少字节,它后面紧跟的是长度为 length 字节的使用 UTF-8 缩略编码表示的字符串。该常量的 length 为 0x0010,十进制是 16,也就是后面的 16 个字节,内容为“java/lang/Object”,图如下
当然如果不想这么麻烦自己算,JDK 为我们提供了 javap 命令,如下