什么是 Class 常量池,和运行时常量池区别是什么?

#Java#面试八股

什么是 Class 常量池,和运行时常量池区别是什么?

结论先行:**Class 常量池(constant pool)**是 .class 文件里的一张表,存字面量与符号引用,是静态的磁盘结构;**运行时常量池(runtime constant pool)**是类加载后这张表在方法区(JDK 8+ 为元空间)里的运行时表示,除了原有的符号引用,还会在解析后换成直接引用,并支持运行期新增常量(如 String.intern()、动态语言调用点)。一句话:前者是字节码里的表,后者是 JVM 里可变的池。

一、三层关系

Hello.class(磁盘)
└── constant_pool 表:CONSTANT_Utf8 / Integer / Class / Methodref ...
        │ 类加载(加载阶段)
        ▼
运行时常量池(方法区 / 元空间,JDK 8+)
├── 符号引用(尚未解析)→ 解析后成为直接引用(指向内存地址)
├── 字符串字面量 → 落地到字符串常量池(堆内 StringTable)
└── 运行期可新增:String.intern()、invokedynamic 的动态常量
维度Class 常量池运行时常量池
位置.class 文件内方法区(JDK 7 前为永久代,JDK 8+ 为元空间)
生命周期随文件存在随类加载创建、类卸载销毁
是否可变不可变可解析、可新增
存放内容字面量、符号引用(类名/字段名/方法名/描述符)符号引用、直接引用、动态生成的常量
数量每个类一份,编译期固定每个类一份,运行期可能增长

二、从字节码看常量池

public class PoolDemo {
    static final int MAX = 100;                  // 编译期常量,进常量池
    static final String NAME = "pool";           // 字面量,进常量池

    public static void main(String[] args) {
        String a = "hello";                      // 字面量,常量池 + 字符串池
        String b = new String("hello");          // 堆上新对象,字面量复用
        System.out.println(a == b);              // false
        System.out.println(a == b.intern());     // true
    }
}
javap -v PoolDemo.class | sed -n '/Constant pool/,/^{/p'

输出片段(节选):

#1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
#4 = String             #5             // hello
#6 = Fieldref           #7.#8          // java/lang/System.out:Ljava/io/PrintStream;
#9 = Class              #10            // java/lang/String

String 项只是一个索引,真正的内容在对应的 Utf8 项里;Methodref 里的 java/lang/Object 与 "<init>":()V 就是符号引用,运行时才被解析成方法入口地址。

三、运行期新增常量

public class PoolGrow {
    public static void main(String[] args) {
        // 编译期不存在这个字面量,运行期通过 intern 动态加入字符串池
        StringBuilder sb = new StringBuilder("dy").append("namic");
        String s = sb.toString();                  // 堆对象
        System.out.println(s == s.intern());       // true(首次 intern 会把引用放入池)
    }
}

invokedynamic 也是运行时常量池可变的体现:Lambda 表达式在编译期只留下一个 InvokeDynamic 常量项,真正的调用点(CallSite)在首次执行时由 LambdaMetafactory 生成并绑定。

四、常见追问

追问回答要点
常量池里一定都是常量吗不,还有符号引用(类、字段、方法、接口方法、方法类型等),它们是「待解析的地址」
new String("x") 创建几个对象字面量首次出现时进池 1 个,堆上 1 个,共 2 个;字面量已存在则只创建堆对象
JDK 7 为什么把字符串池挪到堆永久代大小固定且 GC 效率低,字符串数量大,放堆里更容易回收、避免 PermGen space
运行时常量池会 OOM 吗会,-XX:MaxMetaspaceSize 过小或大量动态生成内容时抛 OutOfMemoryError: Metaspace
常量折叠发生在哪个阶段javac 编译期,final 基本类型与字符串字面量的运算会直接算出结果写进常量池

小结

Class 常量池是「文件里的静态表」,运行时常量池是「内存里可变的池」,字符串常量池则是前者中字符串字面量在堆里的落点。三层分清,javap -v 的输出就不再是天书。

笔记加载中…