Java字符串常量池位置随JDK版本演进:JDK6及以前在永久代(存对象本身),JDK7起移至堆内存(仅存引用),JDK8+仍位于堆中,与元空间分离;核心动因是解决PermGen OOM、提升GC效率并适配方法区重构。

Java 中 String 常量池的位置不是一成不变的,它随 JDK 版本演进经历了三次关键调整,核心动因是解决内存溢出、提升 GC 效率,并适配方法区实现的重构。
JDK 6 及以前:常量池在永久代,存的是字符串对象本身
此时字符串常量池属于方法区,而 HotSpot 虚拟机用永久代(PermGen)来实现方法区。所有双引号字面量(如 "abc")和调用 intern() 的字符串,都会在永久代中创建完整对象实例。
- 永久代空间小(默认几十 MB),且仅在 Full GC 时回收,极易触发 java.lang.OutOfMemoryError: PermGen space
- intern() 会把堆中字符串拷贝一份到永久代,造成冗余副本
- StringTable 默认大小仅 1009,哈希冲突多时性能下降明显
JDK 7:迁入堆内存,只存引用,不复制对象
这是最关键的转变。字符串常量池整体从永久代搬到了 Java 堆中,存储方式也从“存对象”变为“存引用”。
- 字符串对象本体(无论字面量还是 new 出的)统一在堆中分配;常量池只是用哈希表(StringTable)保存这些对象的引用
- 堆支持 Minor GC 和 Full GC,无用字符串可被及时回收,OOM 风险大幅降低
- intern() 不再新建对象,而是登记已有堆对象的引用;若已存在相同内容字符串,则直接复用
- StringTable 默认容量扩大到 60013,可通过 -XX:StringTableSize 调整
JDK 8 及以后:仍在堆中,与元空间彻底分离
JDK 8 废除永久代,引入元空间(Metaspace)存放类元数据、运行时常量池等,但字符串常量池并未进入元空间,仍稳居堆内存中。
立即学习“Java免费学习笔记(深入)”;
- 运行时常量池(来自 .class 文件的符号引用部分)进了元空间;字符串常量池仍由堆中的 StringTable 管理
- 调用大量 intern() 触发的 OOM 错误是 java.lang.OutOfMemoryError: Java heap space,而非 Metaspace 错误——这是最直接的实验证据
- 常量池大小不再受 -XX:MaxPermSize 限制,但受限于堆总大小(-Xmx)
- 元空间只管类结构信息,不适用于字符串高频创建销毁的场景,所以没把它挪过去
本质上,字符串常量池始终依附于方法区的物理实现而变迁,它的位置变化直接改变了 intern() 行为、GC 响应和内存配置策略。


















