
深入浅出 JVM 之字节码-常量池常量分析
文章目录
!版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。
系统环境:
- JDK 1.8
深入浅出 JVM 系列文章
- 01.深入浅出 JVM 之 Java 虚拟机
- 02.深入浅出 JVM 之 Class 字节码文件
- 03.深入浅出 JVM 之字节码-常量池常量分析
- 04.深入浅出 JVM 之字节码-指令集简介
- 05.深入浅出 JVM 之类加载-类加载流程
- 06.深入浅出 JVM 之类加载-类加载器
- 07.深入浅出 JVM 之运行时数据区
- 08.深入浅出 JVM 之运行时数据区-堆
- 09.深入浅出 JVM 之运行时数据区-方法区
- 10.深入浅出 JVM 之运行时数据区-虚拟机栈
- 11.深入浅出 JVM 之运行时数据区-程序计数器
- 12.深入浅出 JVM 之垃圾回收-垃圾回收概述与算法
- 13.深入浅出 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_index 和 name_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_index 和 name_and_type_index。其中,class_index 索引记录了类名,指向常量池中的第 5 个 CONSTANT_Class_info 类型常量;name_and_type_index 索引记录了字段或方法的名称及其类型,指向常量池中的第 16 个 CONSTANT_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 索引,它指向了常量池中的第 17 个 CONSTANT_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_index 和 name_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 索引属性,用于记录类名,指向常量池中的第 4 个 CONSTANT_Class_info 类型常量。另一个是 name_and_type_index 索引,用于记录字段或方法的名称和类型,指向常量池中的第 18 个 CONSTANT_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---
!版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。