String 常量池与 intern
更新时间:2026-09-20。字符串不可变是这一切的前提,见 String 不可变与 StringBuilder 选型。这是
==与equals最容易出玄学的地方。
一、字面量为什么会是同一个对象
String a = "hello";
String b = "hello";
System.out.println(a == b); // true"hello" 是编译期常量,放进 class 的运行时常量池;JVM 在类加载/首次使用时把它驻留(intern)到全局 StringTable(字符串常量池)。相同字面量在池中只有一份,a、b 指向同一个 String 对象,所以 == 为 true。
JDK 7 起 StringTable 从永久代(方法区)移到堆里;JDK 8 永久代被 Metaspace 取代。常量池存的是引用,实际 String 对象在堆上。
二、new String 一定是新对象
String s1 = "x";
String s2 = new String("x");
System.out.println(s1 == s2); // false:new 在堆上新建对象
System.out.println(s1.equals(s2)); // true:内容相同new String("x") 做了两件事:若池中没有字面量 "x" 先入池,再额外在堆上 new 一个字符内容相同的对象。s2 指向这个新对象,和池里那份不是一个地址。
三、intern():入池并返回池引用
String s3 = new String("he").concat("llo"); // 运行期拼出来,内容是 "hello"
System.out.println(s3 == "hello"); // 通常 false:拼接结果不是池对象
System.out.println(s3.intern() == "hello"); // true:intern 返回池里那份引用intern():若池中已有相等字符串,返回池中的引用;否则把当前字符串加入(JDK7+ 是存该堆对象的引用)并返回。- JDK 6 及以前:入池是复制一份到永久代,
intern()返回的是永久代副本,和堆对象==不等; - JDK 7+:池中只存堆对象的引用,首次 intern 后池引用就指向你那个堆对象。这个差异导致网上很多老的 intern 输出示例在新版本结论相反。
四、拼接:编译期折叠 vs 运行期
String c = "a" + "b"; // 编译期常量折叠成 "ab",== "ab" 为 true
final String x = "a";
String d = x + "b"; // x 是编译期常量(final),也折叠成 "ab"
String y = "a";
String e = y + "b"; // y 不是常量,运行期用 StringBuilder/StringFactory 拼接 → 新对象
System.out.println(e == "ab"); // false只有编译期常量表达式(字面量、final 常量)才会折叠;含变量的拼接在运行期生成新对象,== 不可靠。
五、实践纪律
- 比较字符串内容永远用
.equals(或Objects.equals),不要用==。字面量 == 为 true 只是常量池带来的巧合,依赖它会在换成new、变量拼接、反序列化后失效; - 判空常量在前更安全:
"x".equals(s)或Objects.equals("x", s); intern()会让字符串长期驻留 StringTable,不要对海量不可控输入乱 intern(用户 ID、请求参数),池只增不减会放大内存占用甚至 OOM;它主要用于「数量有限、反复出现」的字符串去重;- 性能上循环拼接用
StringBuilder(编译器也会把简单+优化成它,但循环内每次会新建 builder,需手动提出来)。
六、用 javap 验证
javap -c 能看到:常量字符串走 ldc #常量池索引,变量拼接在 JDK9+ 是 invokedynamic makeConcatWithConstants,而比较是 if_acmpne(比引用)。想看字节码的方法见 javap 反汇编。
总结
相同字符串字面量因编译期常量折叠并驻留到堆中的全局 StringTable 而共享同一引用,所以字面量间 == 偶然为真;但 new String 和运行期变量拼接都会产生新对象,== 立刻不可靠,因此比较内容必须用 equals;intern() 返回池引用,JDK7+ 常量池在堆中且只存堆对象引用、行为与 JDK6 复制入永久代不同;除了有限且重复的字符串去重,不要对海量外部输入乱 intern,避免常量池只增不减撑大内存。