Java中String常量池位于堆内存中,JDK 7起从永久代迁移至堆,JDK 8后仍属堆;它是由JVM在堆中分配的哈希表结构,受GC直接管理,与方法区、元空间无关。

Java 中 String 常量池目前明确位于堆内存(Heap)中,不是栈,也不是方法区或永久代。这个结论适用于 JDK 7 及以后所有主流版本(包括 JDK 8、11、17、21 等),且已被 JVM 实现和 GC 行为反复验证。
JDK 版本演进决定位置变化
字符串常量池的物理位置随 JDK 版本升级发生过关键迁移:
-
JDK 1.6 及之前:存放在方法区的永久代(PermGen)中,与类元数据共用空间,容易因字符串过多触发
OutOfMemoryError: PermGen space; - JDK 1.7 开始:常量池整体迁移到堆内存,但存储内容变为“对堆中字符串对象的引用”,而非字符串对象本身;
- JDK 1.8 及以后:永久代被元空间(Metaspace)取代,方法区概念弱化,而字符串常量池仍保留在堆中,逻辑和物理归属均属堆。
为什么说它在堆里,而不是“方法区”或“运行时常量池”?
需区分两个易混淆概念:
- 编译期常量池(Class 文件中的 constant_pool):存在于 .class 文件里,属于静态结构,加载后进入 JVM 的运行时常量池;
- 运行时常量池(Runtime Constant Pool):是方法区的一部分,存放类/接口的符号引用等;
- 字符串常量池(String Table):是 JVM 内部独立维护的一个哈希表结构,从 JDK 7 起,它被实现在堆上,由 JVM 堆管理器统一调度,受垃圾回收器(如 G1、ZGC)直接管理——这是最有力的证据:只有堆中对象才会被 GC 扫描并回收。
技术佐证:它确实在堆中运行
可通过以下方式确认其堆属性:
立即学习“Java免费学习笔记(深入)”;
- 使用
jmap -histo <pid>或 JFR(Java Flight Recorder)可观察到字符串对象及 StringTable 条目出现在堆统计中; - 调用
String.intern()后的对象,若原字符串已无强引用,GC 可将其从字符串常量池中移除——说明该池受堆 GC 控制; - JVM 参数
-XX:StringTableSize调整的是这个哈希表的桶数量,但不改变其内存归属;它仍是堆内分配的 native 层哈希结构,底层内存由堆分配器提供。
常见误区澄清
有些资料提到“在方法区”或“运行时常量池”,是混淆了历史版本或概念层级:
- “方法区”在 HotSpot 中早期对应永久代,现已取消;当前元空间只存类元数据,不存字符串;
- “运行时常量池”包含多种条目(类名、字段名、方法名等),但字符串字面量在 JDK 7+ 后不再存于其中,而是交由独立的 String Table 管理,该表位于堆;
- 所谓“本地哈希表”(native hash table)是指 JVM 用 C++ 实现的高效查找结构,不是指内存位于 Native Memory(堆外),其内存页仍由 JVM 堆子系统分配和回收。


















