深入浅出 JVM 之字节码-常量池常量分析

深入浅出 JVM 之字节码-常量池常量分析

文章目录

  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。

系统环境:

  • JDK 1.8

深入浅出 JVM 系列文章

一、分析常量池说明

在之前的文章中,我们探讨了字节码文件的结构,并简述了常量池的相关概念,但未进行深入分析。因此,本文将详尽介绍如何对常量池中包含的各类常量进行分析。

二、常量池类型结构表

在深入分析常量池中的常量之前,这里先对各种常量的类型进行汇总。下表列出了不同类型的常量,可以帮助大家了解每种常量的功能及属性。具体表格如下:

标志 常量 描述 细节 长度 细节描述
1 CONSTANT_utf8_info UTF-8 编码的字符串 tag u1 值为1
length u2 UTF-8 编码的字符串占用的字符数
bytes u1 长度为 length 的 UTF-8 编码的字符串
3 CONSTANT_Integer_info 整型字面量 tag u1 值为3
bytes u4 按照高位在前存储的 int 值
4 CONSTANT_Float_info 浮点型字面量 tag u1 值为4
bytes u4 按照高位在前存储的 float 值
5 CONSTANT_Long_info 长整型字面量 tag u1 值为5
bytes u8 按照高位在前存储的 long 值
6 CONSTANT_Double_info 双精度浮点型字面量 tag u1 值为6
bytes u8 按照高位在前存储的 double 值
7 CONSTANT_Class_info 类或接口的符号引用 tag u1 值为7
name_index u2 指向全限定名常量项的索引
8 CONSTANT_String_info 字符串类型字面量 tag u1 值为8
string_index u2 指向字符串字面量的索引
9 CONSTANT_Fieldref_info 字段的符号引用 tag u1 值为9
class_index u2 指向声明字段的类或接口描述符 CONSTANT_Class_info 的索引项
name_and_type_index u2 指向字段描述符 CONSTANT_NameAndType_info 的索引项
10 CONSTANT_Methodref_info 类中方法的符号引用 tag u1 值为10
class_index u2 指向声明字段的类或接口描述符 CONSTANT_Class_info 的索引项
name_and_type_index u2 指向名称及类型描述符 CONSTANT_NameAndType_info 的索引项
11 CONSTANT_InterfaceMethodref_info 接口中方法的符号引用 tag u1 值为11
class_index u2 指向声明方法的接口描述符 CONSTANT_Class_info 的索引项
name_and_type_index u2 指向字段描述符 CONSTANT_NameAndType_info 的索引项
12 CONSTANT_NameAndType_info 字段或方法的符号引用 tag u1 值为12
name_index u2 指向该字段或方法名称常量项的索引
descriptor_index u2 指向该字段或方法描述符常量项的索引
15 CONSTANT_MethodHandle_info 表示方法句柄 tag u1 值为15
reference_kind u1 值必须在1-9之间,它决定了方法句柄的类型。方法句柄的类型的值表示方法句柄的字节码行为
reference_index u2 值必须是对常量池的有效索引
16 CONSTANT_MethodType_info 标志方法类型 tag u1 值为16
descriptor_index u2 值必须是对常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_Utf8_info 结构,表示方法的描述符
18 CONSTANT_InvokeDynamic_info 表示一个动态方法调用点 tag u1 值为18
bootstrap_method_attr u2 值必须是对当前 Class 文件中引导方法表的 bootstrap_methods[] 数组的有效索引
name_and_type_index u2 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_NameAndType_info 结构,表示方法名和方法描述符

三、示例 Java 代码

这里贴一个示例的 Java 示例代码,内容如下:

 1public class Test {
 2
 3    private int a;
 4    private String b = "mydlq";
 5
 6    public void a() {
 7        byte i = 15;
 8        int j = 8;
 9        int k = i + j;
10    }
11
12}

接下来使用 javac 命令,将 Java 代码编译为 class 字节码文件:

1$ javac Test

四、使用 Notepad++ 观察字节码文件

使用 Notepad++ 工具打开编译后的字节码文件,然后借助 HEX-Editor 插件查看文件在 十六进制 形式下的内容。具体显示的内容如下所示:

五、分析常量池计数器

在分析字节码中的常量池时,我们首先要分析的就是从字节码开头起的第 9 个字节,该位置存储的就是常量池计数器 constant_pool_count 的值。比如,示例字节码文件中第 9 个字节的位置,如下图所示:

从图中可以看出,常量池计数器在十六进制下的值为 15,这对应于十进制中的 21。不过需要注意的是,常量池的计数从 1 开始,而不是 0。因此,通过计算 constant_pool_count = 21 - 1,我们可以确定常量池中实际包含的常量数量为 20

六、分析常量池表

6.1 分析第一个常量

接下来将对常量池中的第一个常量进行分析,不过在分析常量池表中的常量之前,需要先说明一下。在每个常量中的第 1 个字节是 "类型标记",这个字节称为 tag byte,主要用于确定常量的类型。我们只有先确定了常量的类型之后,才能确定该常量使用了多少字节存储数据。

在 "常量池计数器" 后的数据就是常量的内容,由于不同类型的常量具有不同的属性,因此必须要根据常量的类型来具体分析。这里先对第一个常量进行分析,首先就是先确定常量的 tag 属性值,只要确定了 tag 的属性值后,才能确定该常量的类型。

在常量池表中,第一个常量的 tag 属性值为 0a,这对应于十进制中的 10。根据之前列出的 "常量类型结构表" 我们可以知道,序号10 的常量类型为 CONSTANT_Methodref_info,表中对该类型的常量描述如下:

标志 常量 描述 细节 长度 细节描述
   10    CONSTANT_Methodref_info 类中方法的符号引用 tag   u1   值为10
class_index   u2   指向声明字段的类或接口描述符 CONSTANT_Class_info 的索引项
name_and_type_index   u2   指向名称及类型描述符 CONSTANT_NameAndType_info 的索引项

根据 "常量类型结构表" 中的信息,可以了解到 CONSTANT_Methodref_info 类型的常量包含两个索引项属性,分别为 class_indexname_and_type_index。在当前常量中,这两个索引属性分别指向了特定的索引,如下:

索引 十六进制值 十进制值 指向的索引 说明
class_index 0x0005 5 CONSTANT_Class_info 指向常量池第5个常量
name_and_type_index 0x0010 16 CONSTANT_NameAndType_info 指向常量池第16个常量

通过上述分析,我们可以确定第一个常量属于 CONSTANT_Methodref_info 类型,并且总共占用了 5字节。此类型常量包括两个索引项属性: class_indexname_and_type_index。其中,class_index 索引记录了类名,指向常量池中的第 5CONSTANT_Class_info 类型常量;name_and_type_index 索引记录了字段或方法的名称及其类型,指向常量池中的第 16CONSTANT_NameAndType_info 类型常量。

6.2 分析第二个常量

接下来,我们继续分析第二个常量。该常量开头的第一个字节是 08,即 tag = 08,转换为十进制为 8,如下图所示:

通过参照 "常量类型结构表",我们可以了解到 序号8 的常量类型为 CONSTANT_String_info。表中对该类型的常量描述如下:

标志 常量 描述 细节 长度 细节描述
   8    CONSTANT_String_info 字符串类型字面量 tag   u1   值为8
string_index   u2   指向字符串字面量的索引

根据表中的信息可以了解到,CONSTANT_String_info 类型的常量主要用于记录字符串类型字面量,它只包含了一个索引项属性 string_index,分析常量后的内容如下表所示:

索引 十六进制值 十进制值 指向的索引 说明
string_index 0x0011 17 CONSTANT_utf8_info 指向常量池第17个常量

通过上述分析,我们可以确定第二个常量属于 CONSTANT_String_info 类型,这种类型的常量包含一个指向字符串字面量的 string_index 索引,它指向了常量池中的第 17CONSTANT_Utf8_info 类型常量。此常量记录了字符串值 "mydlq",如下图所示:

6.3 分析第三个常量

我们继续分析第三个常量。该常量开头的第一个字节是 09,即 tag = 09,转换为十进制为 9,如下图所示:

通过参照 "常量类型结构表",我们可以了解到 序号9 的常量类型为 CONSTANT_Fieldref_info。表中对该类型的常量描述如下:

标志 常量 描述 细节 长度 细节描述
   9    CONSTANT_Fieldref_info 字段的符号引用 tag   u1   值为9
class_index   u2   指向声明字段的类或接口描述符 CONSTANT_Class_info 的索引项
name_and_type_index   u2   指向字段描述符 CONSTANT_NameAndType_info 的索引项

根据表中的信息可以了解到,CONSTANT_Fieldref_info 类型的常量主要用于描述字段的符号引用,其包含俩个索引项属性,分别为 class_indexname_and_type_index,详情如下表所示:

索引 十六进制值 十进制值 指向的索引 说明
class_index 0x0004 4 CONSTANT_Class_info 指向常量池第4个常量
name_and_type_index 0x0012 18 CONSTANT_NameAndType_info 指向常量池第18个常量

通过上述分析,我们可以确定第三个常量属于 CONSTANT_Fieldref_info 类型,总共占用了 5字节。该类型的常量包含了俩个索引项: 一个是 class_index 索引属性,用于记录类名,指向常量池中的第 4CONSTANT_Class_info 类型常量。另一个是 name_and_type_index 索引,用于记录字段或方法的名称和类型,指向常量池中的第 18CONSTANT_NameAndType_info 类型常量。

6.4 分析第四个常量

我们继续分析第四个常量。该常量开头的第一个字节是 07,即 tag = 07,转换为十进制为 7,如下图所示:

通过参照 "常量类型结构表",我们可以了解到 序号7 的常量类型为 CONSTANT_Class_info。表中对该类型的常量描述如下:

标志 常量 描述 细节 长度 细节描述
   7    CONSTANT_Class_info 用类或接口的符号引 tag  u1  值为7
name_index  u2  指向全限定名称常量项的索引

根据表中的信息可以了解到,CONSTANT_Class_info 类型的常量主要用于描述类或接口的符号引,它只包含了一个指向全限定名称常量的索引项 name_index ,详情如下表所示:

索引 十六进制值 十进制值 指向的索引 说明
name_index 0x0013 19 CONSTANT_Utf8_info 指向常量池第19个常量

通过上述分析,我们可以确定第四个常量属于 CONSTANT_utf8_info 类型,总共占用了 3字节。该类型的常量包含了一个索引项属性 name_index,其指向了常量池中第 19 个类型为 CONSTANT_Utf8_info 的常量,该常量记录了类或接口的全限定名称 Test,如下图所示:

七、示例常量池中的全部常量

鉴于常量池中包含多达 20 多个常量,这里就不一一进行分析,如果感兴趣的话可以自行尝试分析。这里直接给出分析后的结果,汇总如下:

常量索引号 细节
#1 值: 0a 00 05 00 10
tag: 0a → 10 → CONSTANT_Methodref_info
class_index: 00 05 → 5 → 指向变量 #5
name_and_type_index: 00 10 → 16 → 指向变量 #16
#2 值: 08 00 11
tag: 08 → 8 → CONSTANT_String_info
string_index: 00 11 → 17 → 指向变量 #17
#3 值: 09 00 04 00 12
tag: 09 → 9 → CONSTANT_Fieldref_info
class_index: 00 04 → 4 → 指向变量 #4
name_and_type_index: 00 12 → 18 → 指向变量 #18
#4 值: 07 00 13
tag: 07 → 7 → CONSTANT_Class_info
name_index: 00 13 → 19 → 指向变量 #19
#5 值: 07 00 14
tag: 07 → 7 → CONSTANT_Class_info
name_index: 00 14 → 20 → 指向变量 #20
#6 值: 01 00 01 61
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 01 → 1
bytes: 61 → a
#7 值: 01 00 01 49
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 01 → 1
bytes: 49 → I
#8 值: 01 00 01 62
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 01 → 1
bytes: 62 → b
#9 值: 01 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 12 → 18
bytes: 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b → Ljava/lang/String;
#10 值: 01 00 06 3c 69 6e 69 74 3e
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 06 → 6
bytes: 3c 69 6e 69 74 3e → <init>
#11 值: 01 00 03 28 29 56
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 03 → 3
bytes: 28 29 56 → ()V
#12 值: 01 00 04 43 6f 64 65
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 04 → 4
bytes: 43 6f 64 65 → Code
#13 值: 01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 0f → 15
bytes: 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 → LineNumberTable
#14 值: 01 00 0a 53 6f 75 72 63 65 46 69 6c 65
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 0a → 10
bytes: 53 6f 75 72 63 65 46 69 6c 65 → SourceFile
#15 值: 01 00 09 54 65 73 74 2e 6a 61 76 61
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 09 → 10
bytes: 54 65 73 74 2e 6a 61 76 61 → Test.java
#16 值: 0c 00 0a 00 0b
tag: 0c → 12 → CONSTANT_NameAndType_info
name_index: 00 0a → 10 → 指向变量 #10
descriptor_index: 00 0b → 11 → 指向变量 #11
#17 值: 01 00 05 6d 79 64 6c 71
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 05 → 5
bytes: 6d 79 64 6c 71 → mydlq
#18 值: 0c 00 08 00 09
tag: 0c → 12 → CONSTANT_NameAndType_info
name_index: 00 08 → 8 → 指向变量 #8
descriptor_index: 00 09 → 9 → 指向变量 #9
#19 值: 01 00 04 54 65 73 74
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 04 → 4
bytes: 54 65 73 74 → Test
#20 值: 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74
tag: 01 → 1 → CONSTANT_Utf8_info
length: 00 10 → 16
bytes: 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 → java/lang/Object

---END---


  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。