字符串常量池、运行时常量池和静态常量池是三个独立结构:静态常量池存于.class文件(磁盘),含字面量和符号引用;运行时常量池在方法区/元空间,存类结构直接引用;字符串常量池自jdk7起位于堆中,仅存字符串对象引用,三者位置、内容、生命周期均不同。

字符串常量池、运行时常量池和静态常量池,名字相近但职责不同、位置各异。它们不是同一块内存的三种叫法,而是编译期、类加载期、运行期三个阶段中各自独立存在的结构。理解它们的关键,在于分清“在哪存”和“存什么”。
静态常量池:只在 .class 文件里,不进 JVM 内存
它就是编译器生成的字节码文件(.class)中那个 Constant pool table,属于磁盘上的静态数据。里面存的是编译期确定的字面量(比如 "hello"、123)和符号引用(如类名、方法名、字段名)。它不占用 JVM 运行时内存,也不会被 GC 管理。只要 .class 文件存在,它就存在;JVM 加载类时,会读取它,但不会把它整个搬进内存。
运行时常量池:随方法区走,从永久代到元空间
它是静态常量池在 JVM 中的运行时映像,属于方法区的一部分:
- JDK 6 及以前:方法区由永久代实现 → 运行时常量池在永久代中
- JDK 7:方法区仍用永久代,但已开始“瘦身”→ 运行时常量池还在永久代,但其中的字符串字面量被剥离出去了
- JDK 8 及以后:永久代被移除,方法区改由元空间(Metaspace)实现 → 运行时常量池整体迁入元空间
它存的是类结构信息的直接引用(比如已解析的方法地址)、动态生成的常量(如调用 `Integer.valueOf(100)` 返回的缓存对象),但不再存字符串对象本身。它的生命周期与类绑定,类卸载时才可能回收。
字符串常量池:专管字符串复用,JDK 7 起稳居堆中
它和运行时常量池同源(都源于 .class 中的字符串字面量),但运行期完全独立,目标只有一个:保证相同内容的字符串对象唯一。它的位置演变是 JVM 内存优化的重点:
- JDK 6 及以前:和运行时常量池挤在永久代 → 存的是字符串对象实例本身 → 容量小、GC 不友好,易触发 OutOfMemoryError: PermGen space
- JDK 7:整体迁入 Java 堆 → 存的是堆中字符串对象的引用 → 可参与新生代 Minor GC,大幅缓解内存压力
- JDK 8 及以后:继续留在堆中,与元空间解耦 → 运行时常量池在元空间,字符串常量池在堆,物理分离、各司其职
实验证据很直接:持续调用 `String.intern()` 在 JDK 8 下抛出的是 OutOfMemoryError: Java heap space,而不是 PermGen 错误,这直接印证了它就在堆里。
为什么字符串常量池没跟着去元空间?
因为元空间只管类元数据——类定义、方法签名、字段描述这些长期稳定的内容;而字符串生命周期短、数量大、创建销毁频繁,和元空间的设计定位完全相反。堆才是它最自然的归属地。










