运行时常量池是方法区(或元空间)在程序运行时动态分配的一块内存区域,用于存放编译期生成的各种字面量和符号引用;而常量池通常指 class 文件中的 .class 静态资源部分。两者的核心区别在于存在阶段不同:常量池存在于磁盘上的字节码文件中,是静态的;运行时常量池存在于 jvm 内存中,是动态的,且在类加载过程中会将字符串常量等加入 java 堆(string table)。

常量池在 .class 文件中存储哪些内容
常量池位于 .class 文件的固定结构之后,主要包含两类数据:字面量(Literal)和符号引用(Symbolic References)。字面量包括文本字符串、被声明为 final static 的数值常量等;符号引用则包括类和接口的全限定名、字段的名称和描述符、方法的名和描述符。这些内容在编译期确定,直接写入磁盘文件。

public class ConstantPoolDemo {
public static final int MAX_VALUE = 100;
public void show() {
String s = "hello";
}
}
上述代码编译后,其 .class 文件中的常量池会包含以下条目:
1. #1 CONSTANT_Class: 指向 ConstantPoolDemo。
2. #2 CONSTANT_String: 值为 "hello"。
3. #3 CONSTANT_Integer: 值为 100。
4. #4 CONSTANT_Methodref: 指向 java/lang/Object.<init></init>。
5. #5 CONSTANT_NameAndType: 指向 show 方法签名。
立即学习“Java免费学习笔记(深入)”;
运行时常量池在 JVM 中如何动态扩展
运行时常量池在 JDK 1.7 之前位于方法区(HotSpot 虚拟机中的永久代),1.7 及之后位于元空间(Metaspace)。它在类加载的解析阶段创建,并允许将新的常量放入池中,这是 Java 语言具备动态性的基础之一。例如,通过 String.intern() 可以将运行时创建的字符串加入运行时常量池。如果运行时常量池无法再申请到内存,则会抛出 OutOfMemoryError: Metaspace 或 OutOfMemoryError: PermGen space。

public class RuntimeConstantPoolDemo {
public static void main(String[] args) {
String s1 = new String("test");
String s2 = s1.intern();
System.out.println(s1 == s2);
}
}
在上述代码中,new String("test") 会在堆中创建一个新对象,而 s1.intern() 会尝试将 "test" 放入运行时常量池。如果常量池中已存在该字符串,则返回其引用;否则将其加入并返回引用。在 JDK 1.7+ 中,若 "test" 尚未在常量池中,intern() 会将堆中的对象引用复制到常量池,因此 s1 == s2 结果为 true。这体现了运行时常量池的动态性。
基于 String.intern() 调用结果的具体问题
当调用 String.intern() 时,如果字符串常量池中已经包含一个等于此 String 对象的字符串,则直接返回池中的引用;否则,将此 String 对象添加到池中,并返回对该对象的引用。这一行为决定了 s1 == s2 的比较结果。在 JDK 1.6 中,常量池在永久代,intern() 会复制字符串到永久代,因此 s1 == s2 为 false;而在 JDK 1.7+ 中,常量池在元空间,但 intern() 优化为直接返回堆中对象的引用,因此 s1 == s2 为 true。开发者需注意版本差异对内存引用的影响。


















