字符串常量池(stringtable)在jvm中经历三次迁移:jdk6及以前位于永久代并存储对象本身;jdk7起迁入堆内存,仅存储引用;jdk8及以后仍驻堆中,与元空间分离,且jdk9起因compact strings优化内存与gc效率。

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











