运行时常量池的数据直接来自class文件常量池,是类加载时“搬运”并“活化”的结果;其唯一源头是编译期生成的class文件常量池表,包含字面量与符号引用;加载时复制到元空间并完成符号引用到直接引用的解析;支持String.intern()等有限动态扩充,但与字符串常量池物理分离、协同工作。

运行时常量池的数据直接来自 class 文件中的常量池(也称静态常量池),它不是独立构造的,而是类加载过程中被“搬运”并“活化”的结果。关键不在于“存哪里”,而在于“怎么来、怎么用”。
数据来源:class 文件常量池是唯一源头
每个 class 文件开头就有一个结构化的常量池表,由 constant_pool_count 和后续的 cp_info 数组构成。它在编译期生成,内容固定,包括:
- 字面量:字符串文本(如
"hello")、基本类型字面值(如100、true)、final 静态字符串或基本类型常量(满足编译期确定条件) - 符号引用:类全限定名(
java/lang/Object)、字段描述符(out:Ljava/io/PrintStream;)、方法签名("toString()Ljava/lang/String;")等
加载过程:从静态到运行时的三步映射
类加载器将 class 文件读入后,在“解析阶段”完成常量池内容的迁移与转化:
- 复制入方法区:JDK 7 及以前复制到永久代;JDK 8+ 复制到元空间,形成该类专属的运行时常量池
-
符号引用转直接引用:比如把常量池中 #5 的
java/lang/System字符串索引,替换成指向对应Class对象的内存地址 - 延迟解析:并非所有符号引用都在加载时解析,部分(如某些接口方法)可能推迟到首次使用时才解析
运行时行为:支持有限动态扩充
运行时常量池不是只读快照,但扩充有明确边界:
-
String.intern()调用可能向当前类关联的运行时常量池中注册新的CONSTANT_String_info条目(实际效果取决于 JVM 实现,HotSpot 中仅当字符串已存在于字符串常量池时才会触发该类池项的解析) - 反射调用(如
Class.forName())或动态代理生成的新类,其自身也会拥有独立的运行时常量池,但不会往已有类的池中写入非字符串类的新符号引用 - 普通
new String("abc")不会往运行时常量池加项,只影响堆和字符串常量池
与字符串常量池的关系:协同但分离
两者物理位置和作用范围完全不同,但字符串处理是它们最典型的交点:
- 运行时常量池位于元空间,按类隔离,存的是
CONSTANT_String_info(含一个指向CONSTANT_Utf8_info的索引) - 字符串常量池是全局哈希表,位于堆(JDK 7+),存的是字符串对象的引用
- 执行
String s = "abc"时:先查字符串常量池 → 若无则创建对象并入池 → 同时触发对该类运行时常量池中对应项的解析,使其指向该堆对象

















