深入浅出 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---
!版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。