字符串常量池是堆中固定大小的stringtable哈希表(jdk1.8+默认容量60013),存储堆中字符串对象的引用;双引号创建时通过哈希定位、equals比对实现复用,new string()强制新建堆对象且不自动复用,运行时拼接字符串需显式调用intern()才可能入池。

字符串常量池本质是堆里的 StringTable 哈希表
它不是魔法,而是一个固定大小的 HashTable(JDK1.7+),默认容量 60013(JDK1.8+),存储的是 堆中字符串对象的引用,不是字符串本身。key 是字符串内容(char[] 或 byte[] 的哈希值),value 是该字符串在堆中的地址引用。
所以“重用”不是复制对象,而是让多个变量指向堆里同一个 String 实例——前提是它们都通过双引号字面量创建,且内容完全一致。
双引号赋值时 JVM 怎么查表并复用
执行 String s = "abc" 时,JVM 不会直接 new 对象,而是走以下流程:
- 计算
"abc"的哈希值,定位到StringTable对应桶(bucket) - 遍历该桶下的链表/红黑树,用
equals()逐个比对内容(不是只比哈希值) - 命中则直接返回已有对象的引用;未命中则在堆中新建
String,再把其引用存入表中
这个过程在类加载的 ldc 指令阶段完成,属于运行时常量池解析的一部分,速度接近 O(1)。
new String("abc") 为什么不能自动复用
new 强制在堆上分配新对象,和常量池无关:
- 先在堆创建一个全新
String实例(即使内容相同) - 再检查常量池:若无
"abc",会把字面量"abc"加入池中(但不改变刚 new 出来的对象) - 最终返回的是堆对象引用,不是常量池里那个——所以
new String("abc") == "abc"为false
注意:new String("abc").intern() 在 JDK1.7+ 会返回常量池中已有的引用(即和 "abc" 相同),但这是显式调用,不是自动行为。
容易被忽略的关键点:常量池只管字面量,不管运行时拼接
像 String s = "ab" + "c" 这种编译期可确定的拼接,会被 javac 优化成 "abc",走常量池;但 String s = "ab" + getStr() 或 new StringBuilder().append("ab").toString() 生成的字符串,哪怕内容相同,也不会自动进常量池,除非手动调用 intern()。
另外,StringTable 大小有限(默认 60013),冲突多或字符串过多时会发生哈希碰撞,影响查找效率;可通过 -XX:StringTableSize=120029 调整,但不能动态扩容。











