字符串常量池在jvm中位于堆内存,jdk 7起从永久代迁移至此,jdk 8及以后仍属堆;它是由jvm维护的哈希表(stringtable),存储字符串对象引用,受gc直接管理,非独立内存区域而是逻辑概念。

字符串常量池在 JVM 中的内存分配,核心取决于 JDK 版本和创建方式。它不是独立内存区域,而是 JVM 运行时数据区中一个逻辑上的“池”,实际位置随版本迁移,作用始终是复用字符串、减少堆压力。
位置变化:从方法区到堆
JDK 6 及以前,字符串常量池位于方法区(永久代);JDK 7 开始,它被整体移到堆内存中;JDK 8 及以后延续这一设计,常量池作为堆的一部分由垃圾回收器统一管理。虽然物理位置变了,但“常量池”仍是逻辑概念——它是一张全局共享的哈希表(StringTable),默认大小为 60013(JDK 7+),可通过 -XX:StringTableSize 调整。
内容来源:哪些字符串会进池?
只有满足“编译期可确定”的字符串字面量才会自动入池:
- 直接用双引号声明:如 String s = "hello" → 编译时就加入常量池
- 纯字面量拼接:如 "ab" + "c" → 编译器优化为 "abc",同样进池
- static final 字符串常量(值在编译期已知)也会进入
而运行时生成的字符串(如 new String("abc")、StringBuilder.toString()、用户输入等)默认不在池中,除非显式调用 intern()。
intern() 的作用:手动登记进池
intern() 是唯一能将堆中字符串“注册”到常量池的方法:
- 调用时,JVM 检查池中是否存在内容相同的字符串
- 存在 → 直接返回池中已有引用
- 不存在 → 把该字符串内容(注意是内容,不是对象)加入池,并返回池中引用
例如:new String("test").intern() == "test" 恒为 true,因为后者字面量已在池中,前者通过 intern() 获取到同一引用。
内存影响与注意事项
常量池虽在堆中,但其中字符串对象生命周期长,GC 不会轻易回收(尤其被大量 intern 的字符串),可能引发内存占用上升;哈希表过小或大量重复 intern 会导致哈希冲突加剧、性能下降。因此,只对高频复用、内容稳定且数量可控的字符串才考虑 intern,比如配置项名、协议字段、枚举标识等。











