1. 简介

当我们编译一个 *.java 文件时,会生成一个扩展名为 *.class 的类文件。这个 .class 文件由多个部分组成,其中一部分就是常量池(Constant Pool)

在本篇文章中,我们将深入探讨 JVM 常量池的相关细节,包括它支持的数据类型以及信息的存储格式。

2. Java 中的常量池

简单来说,常量池是用于保存当前类运行所需的各种常量的数据结构。你可以把它理解为一种运行时使用的符号表(Symbol Table),它是每个类或接口在 .class 文件中的运行时表示形式。

常量池的内容是由编译器生成的符号引用构成的,这些引用包括变量名、方法名、接口名和类名等。JVM 在运行时会使用这些引用去链接当前类与其他依赖类之间的关系。

我们通过一个简单的 Java 类来查看常量池的结构:

public class ConstantPool {
    
    public void sayHello() {
        System.out.println("Hello World");
    }
}

要查看该类的常量池内容,我们需要先进行编译,然后执行如下命令:

javap -v ConstantPool.class

执行后输出如下内容:

   #1 = Methodref          #6.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #17            // Hello World
   #4 = Methodref          #18.#19        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #20            // com/baeldung/jvm/ConstantPool
   #6 = Class              #21            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               sayHello
  #12 = Utf8               SourceFile
  #13 = Utf8               ConstantPool.java
  #14 = NameAndType        #7:#8          // "<init>":()V
  #15 = Class              #22            // java/lang/System
  #16 = NameAndType        #23:#24        // out:Ljava/io/PrintStream;
  #17 = Utf8               Hello World
  #18 = Class              #25            // java/io/PrintStream
  #19 = NameAndType        #26:#27        // println:(Ljava/lang/String;)V
  #20 = Utf8               com/baeldung/jvm/ConstantPool
  #21 = Utf8               java/lang/Object
  #22 = Utf8               java/lang/System
  #23 = Utf8               out
  #24 = Utf8               Ljava/io/PrintStream;
  #25 = Utf8               java/io/PrintStream
  #26 = Utf8               println
  #27 = Utf8               (Ljava/lang/String;)V

注意:#n 表示对常量池中的某项引用。

例如:

  • #17 是字符串 "Hello World" 的符号引用;
  • #18System.out
  • #19println 方法;
  • #8 显示了方法返回值类型为 void
  • #20 则是完整的类名。

⚠️ 还有一个关键点:常量池索引从 1 开始,索引 0 是无效索引。

2.1. 支持的数据类型

常量池支持多种数据类型:

类型 描述
Integer, Float 32 位常量
Double, Long 64 位常量
String 16 位字符串常量,指向池中另一个 UTF8 字符串条目
Class 完整类名
Utf8 字节流,表示字符串
NameAndType 冒号分隔的一对值,分别表示名称和描述符
Fieldref, Methodref, InterfaceMethodref 点号分隔的一对值,分别指向 ClassNameAndType 条目

❓那像 booleanshortbyte 这些基本类型呢?

✅ 这些类型在常量池中统一用 Integer 表示。

2.2. 数据格式

常量池中每个条目的结构都遵循以下通用格式:

cp_info {
    u1 tag;
    u1 info[];
}

其中:

  • tag 是一个字节,用来标识该条目是什么类型的常量;
  • info[] 是后续的实际数据。

举几个常见类型的 tag 值:

类型 Tag 值
Utf8 1
Integer 3
Float 4
Long 5
Double 6
Class 引用 7
String 引用 8

只有当 JVM 完成类加载之后,才会创建对应的常量池。

3. 总结

在这篇文章中,我们了解了 JVM 中的常量池机制。它本质上是一个运行时符号表,保存了类运行所需的各类符号引用。我们也看到了常量池如何组织不同类型的信息,并通过 tag 标识不同的条目类型。

如果你想进一步研究,可以访问 GitHub 获取示例代码: 🔗 https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-jvm-2


原始标题:An Introduction to the Constant Pool in the JVM