字符串常量池从jdk 6永久代→jdk 7堆中(存引用)→jdk 8堆中稳定存在,因永久代易oom、gc低效,而元空间专管类元数据,不适用字符串高频创建销毁特性。

字符串常量池的位置和存储机制在 JDK 6 到 JDK 8 的演进中发生了关键变化,核心动因是解决永久代内存溢出、提升 GC 效率,并适配方法区实现的重构。它不是简单“搬家”,而是牵动内存结构、对象生命周期和 intern() 行为的一系列底层调整。
JDK 1.6 及以前:常量池与对象共存于永久代
字符串常量池(StringTable)是永久代(PermGen)中的一部分,而永久代正是当时 HotSpot 对 JVM 规范中“方法区”的具体实现。此时常量池里直接存放的是字符串对象的实例本身,不是引用。
- 所有双引号字面量(如
"abc")和显式调用intern()的字符串,都会在永久代中创建完整对象 - 永久代默认空间小(通常几十 MB),且仅在 Full GC 时才回收,大量字符串极易触发
java.lang.OutOfMemoryError: PermGen space - StringTable 默认大小为 1009,哈希冲突严重时链表过长,
intern()性能明显下降
JDK 1.7:迁移至堆,存储逻辑发生根本转变
字符串常量池整体从永久代搬到了 Java 堆中,这是最实质性的一步。与此同时,它的内容也从“存对象”变为“存引用”。
- 字符串对象本身(包括字面量)现在统一在堆的 Eden 区等普通区域分配;常量池只保存这些对象的引用地址
- 此举让字符串能参与新生代的高频 Minor GC,大幅缓解内存压力
- StringTable 默认容量扩大到 60013,支持通过
-XX:StringTableSize调整(最小值仍为 1009) -
intern()行为改变:若堆中已存在相同内容字符串,就将它的引用加入常量池;不再复制新对象到常量池
JDK 1.8 及以后:永久代消失,常量池稳居堆中
JDK 8 彻底移除永久代,改用元空间(Metaspace)作为方法区的实现。元空间使用本地内存,不再受 JVM 参数限制,但字符串常量池并未随之进入元空间。
- 字符串常量池继续保留在堆内存中,位置和 JDK 7 完全一致
- 元空间只负责存储类元数据、运行时常量池(Runtime Constant Pool)、字段/方法信息等,不包含任何字符串内容
- 运行时常量池(来自 .class 文件)迁入元空间,而字符串常量池(String Constant Pool)独立留在堆里——二者从此物理分离
- 实验证据明确:持续调用
intern()会触发java.lang.OutOfMemoryError: Java heap space,而非 PermGen 错误
为什么不变动到元空间?
字符串生命周期短、数量大、创建销毁频繁,与类元数据(长期稳定、加载后基本不变)特性截然不同。堆内存具备更灵活的分代回收机制,更适合管理字符串对象及其引用。若强行塞进元空间,反而会破坏其“按需扩展、避免 GC”的设计初衷。









