java中string常量池自jdk7起物理位于堆内存,逻辑独立且由stringtable管理;字面量字符串存于常量池,new创建的string对象在普通堆区,intern()可手动将其加入常量池。

Java 中 String 字符串在 JVM 堆内存中的分布,核心在于“堆内划区”+“逻辑隔离”:JDK 7 及以后,字符串常量池(String Pool)本身已物理迁移至堆内存中,但它仍是堆里一个逻辑独立、受特殊管理的区域;其余 String 对象则按创建方式分散在堆的普通对象区。
常量池现在就在堆里
JDK 6 时它在永久代(PermGen),JDK 7 起正式移入堆内存(Heap)。这意味着:
- 所有通过字面量创建的字符串(如 String s = "hello";)都存放在堆内的字符串常量池中
- 常量池不是堆外区域,也不是方法区残留——它是堆的一部分,只是由 JVM 用 StringTable(基于哈希表实现)单独维护
- 默认大小为 60013 个桶(可通过 -XX:StringTableSize 调整),支持扩容,但不自动清理(除非触发 GC 并满足条件)
new 出来的 String 对象在堆的普通区域
执行 String s = new String("abc"); 时,实际发生两件事:
- 先确保常量池中存在 "abc"(若无则加入)
- 再在堆的普通对象区新建一个 String 实例,其内部 value 字段引用的是常量池中已有的字符数组(char[] 或 byte[]),但该 String 对象本身是全新分配的
这个新对象和常量池里的 "abc" 内存地址不同,所以 s == "abc" 为 false,但内容相同,s.equals("abc") 为 true。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
拼接结果多数落在堆普通区
拼接是否进常量池,取决于编译期能否确定全部操作数:
- "ab" + "cd" → 编译期优化为 "abcd",走常量池
- String a = "ab"; String b = "cd"; a + b → 运行期用 StringBuilder 拼接,结果对象在堆普通区,常量池中不会新增 "abcd"
- final String a = "ab"; final String b = "cd"; a + b → 因变量被 final 修饰且初始化为字面量,编译器视为常量表达式,仍走常量池
intern() 是手动把堆对象“登记”进常量池的唯一方式
调用 new String("test").intern() 会:
- 检查常量池中是否已有内容为 "test" 的字符串
- 有 → 直接返回池中引用
- 没有 → 把当前堆中字符串的内容复制(或直接引用)到常量池,并返回该引用
因此 new String("test").intern() == "test" 恒为 true。这相当于在堆与常量池之间架了一座可控制的桥。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










