【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 文件,如下图

image

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
image

回到上面示例代码 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 个字节。然后继续下一个常量...依次可以找出所有常量,如下图

image

回头看第一个常量的第一个 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”,图如下

image

当然如果不想这么麻烦自己算,JDK 为我们提供了 javap 命令,如下

image

posted @ 2022-06-05 23:51  Tailife  阅读(168)  评论(0编辑  收藏  举报