Skip to content

常量池(计数器/字面量/符号引用)

常量池:Class 文件的核心数据区

常量池是 Class 文件中内容最丰富、结构最复杂的区域。它存放了类、接口、方法、字段的所有符号引用,是连接字节码和运行时常量池的桥梁。

常量池的结构

基本结构

┌─────────────────────────────────────────────┐
│  constant_pool_count (u2)                    │
│  值为:常量数量 + 1                          │
│  (索引 1 ~ count-1 有效,0 保留)           │
├─────────────────────────────────────────────┤
│  constant_pool[0]                           │ ← 不使用
│  constant_pool[1]                           │
│  constant_pool[2]                           │
│  ...                                        │
│  constant_pool[constant_pool_count - 1]     │
└─────────────────────────────────────────────┘

常量池计数

bash
# 查看常量池计数
javap -verbose HelloWorld.class

# 输出:
# Constant pool:
# const #1 = Method ...        ; // #2
# const #2 = class             ; // #3
# ...
# { ... }
#   constant pool size = 15    ← 常量池计数 = 15
#                            ← 实际常量 14 个(索引 1~14)

常量池的 17 种常量类型

JVM 规范定义了 17 种常量类型(JDK 12 增加了 CONSTANT_DynamicCONSTANT_InvokeDynamic):

类型标签说明
CONSTANT_Utf81UTF-8 编码的字符串
CONSTANT_Integer3整型字面量
CONSTANT_Float4浮点字面量
CONSTANT_Long5长整型字面量
CONSTANT_Double6双精度字面量
CONSTANT_Class7类或接口符号引用
CONSTANT_String8字符串字面量引用
CONSTANT_Fieldref9字段符号引用
CONSTANT_Methodref10方法符号引用(非接口)
CONSTANT_InterfaceMethodref11接口方法符号引用
CONSTANT_NameAndType12字段或方法的名称和类型
CONSTANT_MethodHandle15方法句柄
CONSTANT_MethodType16方法类型
CONSTANT_Dynamic17动态计算常量(JDK 12)
CONSTANT_InvokeDynamic18invokedynamic 指令
CONSTANT_Module19模块引用(JDK 9)
CONSTANT_Package20包引用(JDK 9)

常见常量类型详解

1. CONSTANT_Utf8(标签 = 1)

存储所有字符串内容,包括类名、方法名、字段名、描述符等:

CONSTANT_Utf8_info {
    u1  tag;              // 1
    u2  length;           // 字节长度
    u1  bytes[length];     // UTF-8 编码的字节
}

2. CONSTANT_Class(标签 = 7)

引用一个类或接口:

CONSTANT_Class_info {
    u1  tag;              // 7
    u2  name_index;       // 指向 CONSTANT_Utf8 的索引
}

3. CONSTANT_String(标签 = 8)

引用字符串常量(但不是 String 对象本身):

CONSTANT_String_info {
    u1  tag;              // 8
    u2  string_index;     // 指向 CONSTANT_Utf8 的索引
}

4. CONSTANT_NameAndType(标签 = 12)

描述字段或方法的名称和类型:

CONSTANT_NameAndType_info {
    u1  tag;                  // 12
    u2  name_index;           // 名称(指向 Utf8)
    u2  descriptor_index;    // 描述符(指向 Utf8)
}

5. 引用类常量(标签 = 9/10/11)

字段引用、方法引用、接口方法引用:

CONSTANT_Fieldref_info {
    u1  tag;              // 9
    u2  class_index;      // 指向 CONSTANT_Class
    u2  name_and_type_index;  // 指向 CONSTANT_NameAndType
}

CONSTANT_Methodref_info {
    u1  tag;              // 10
    u2  class_index;      // 指向 CONSTANT_Class
    u2  name_and_type_index;  // 指向 CONSTANT_NameAndType
}

常量池的内部链接

常量池的「表驱动」结构

常量池中的各个常量通过索引相互引用:

常量池示例:

const #1 = class          ; // #2
const #2 = Asciz          HelloWorld;

          这是一个 CONSTANT_Utf8
          const #3 = class          ; // #4
          const #4 = Asciz          java/lang/Object;

完整链接:
  const #1(Class)→ name_index = #2(Utf8 = "HelloWorld")
  const #3(Class)→ name_index = #4(Utf8 = "java/lang/Object")

字段引用的完整解析

java
public class User {
    private String name;
    private int age;
}

对应的常量池条目:

#1 = Fieldref            ; // #3.#12
#2 = class               ; // #4
#3 = NameAndType         ; // #5.#6
#4 = class               ; // #7
#5 = Utf8                ; name
#6 = Utf8                ; Ljava/lang/String;
#7 = Utf8                ; com/example/User

方法描述符(Descriptor)

描述符描述字段和方法的类型信息:

字段描述符

类型字符Java 类型
Bbyte
Cchar
Ddouble
Ffloat
Iint
Jlong
Sshort
Zboolean
Vvoid(仅方法返回)
L<classname>;对象引用,如 Ljava/lang/String;
[数组,如 [I(int 数组),[[Ljava/lang/Object;

方法描述符

方法描述符的格式:(参数描述符*)返回描述符

java
// 方法描述符示例

void method()
// → ()V

int add(int a, int b)
// → (II)I

String getName(User user, List&lt;String&gt; names)
// → (Lcom/example/User;Ljava/util/List;)Ljava/lang/String;

boolean isEmpty()
// → ()Z

查看描述符

bash
javap -verbose User.class

# 输出(部分):
# const #3 = NameAndType         ; #9:#10
# const #4 = Asciz               age:I
# const #5 = Asciz               name:Ljava/lang/String;
# ...
# { ...
#   private int age;
#     descriptor: I
#       └─ int 类型
#   private java.lang.String name;
#     descriptor: Ljava/lang/String;
#       └─ String 对象引用
# }

字面量 vs 符号引用

字面量(Literal)

字面量是源码中直接写出的值:

java
int a = 100;           // 整数字面量 100
double d = 3.14;        // 浮点字面量 3.14
String s = "hello";     // 字符串字面量 "hello"

这些值存储在常量池中:CONSTANT_IntegerCONSTANT_FloatCONSTANT_String

符号引用(Symbolic Reference)

符号引用是用符号形式描述的引用,不是直接内存地址:

java
public class User {
    private String name;
    public void setName(String name) { }
}

对应的符号引用:

# 字段符号引用
class_index        → User 类
name_index         → "name"
descriptor_index   → "Ljava/lang/String;"(String 对象引用)

# 方法符号引用
class_index        → User 类
name_index         → "setName"
descriptor_index   → "(Ljava/lang/String;)V"(参数 String,返回 void)

符号引用 vs 直接引用

编译时(Class 文件中):
  使用符号引用(不知道实际内存地址)

运行时(类加载后):
  JVM 解析符号引用 → 直接引用(内存地址)

常量池的内存占用

常量池是 class 文件的一部分,会被加载到方法区/元空间中:

运行时常量池 vs Class 文件常量池:
┌─────────────────────┐    ┌─────────────────────┐
│  Class 文件常量池     │    │  运行时常量池        │
│  (磁盘文件)         │    │  (JVM 内存中)      │
│                     │    │                      │
│  · 静态存储         │───▶│  · 动态添加(intern) │
│  · 编译时确定       │    │  · 运行时常量        │
└─────────────────────┘    └─────────────────────┘

本节小结

常量池的核心要点:

维度说明
位置Class 文件的核心区域,存放所有符号引用
计数constant_pool_count = 常量数量 + 1(索引 0 保留)
17 种类型Utf8、Class、String、Fieldref、Methodref、NameAndType 等
描述符字段类型(Z/C/B/I/J/D/F/L...)和方法类型(参数→返回)
字面量编译时常量(100、"hello")
符号引用类、方法、字段的符号形式(非内存地址)

常量池是理解字节码指令和类加载机制的关键。

下一节,我们来看 访问标识/类索引/父类索引/接口索引

基于 VitePress 构建