面试知识库
进阶

String常量池与intern#

一句话答案#

字符串常量池(StringTable)是一张哈希表,JDK 7 起从永久代移到堆中;字面量自动入池并复用,new String() 必在堆上新建对象不入池,intern() 会把堆中字符串的引用登记进池(已存在则返回池中引用)。

核心要点

1. 常量池是什么、在哪

  • 本质是一张固定容量的哈希表 StringTable,存的是字符串对象的引用。
  • 位置演进:JDK 6 在永久代(PermGen)→ JDK 7/8 移到堆中 → 这样 intern 的字符串能被正常 GC,也避免 PermGen OOM。
  • 容量参数 -XX:StringTableSize(JDK 8 默认 60013),太小会增加哈希冲突、拖慢 intern。

2. 字面量 vs new String

String a = "hello";              // 字面量:编译期进入常量池,复用
String b = "hello";              // 复用池中同一对象
System.out.println(a == b);      // true

String c = new String("hello");  // 运行期在堆上新建对象,不入池
System.out.println(a == c);      // false(c 指向堆对象)
java

3. 经典追问:new String("a") 创建几个对象?

  • 最多两个:一个是堆中 new 出来的 String 对象;一个是字面量 "a" 在常量池中的对象(若池中已有则不再创建,只算一个)。
  • new String("a") + new String("b") 涉及 StringBuilder,对象更多。

4. intern() 机制(JDK 7+)

String s = new String("he") + new String("llo");  // 堆对象,池中此时无 "hello"
System.out.println(s.intern() == s);   // true:池中没有,直接登记 s 的引用
String t = "hello";                    // 复用池中引用(即 s)
System.out.println(t == s);            // true
java
  • JDK 6:intern 会把字符串复制一份放进永久代池,返回池中副本。
  • JDK 7+:池在堆中,intern 直接把堆对象的引用登记进池(无需复制),所以上面 s.intern()==s 成立。

5. 拼接的编译优化

  • "a" + "b" 编译期直接合并为字面量 "ab" 入池。
  • 含变量的拼接:JDK 8 编译为 StringBuilder.append,JDK 9+ 用 invokedynamic + StringConcatFactory
  • 循环内拼接才需手动 StringBuilder(避免每轮 new 一个 StringBuilder)。
面试回答(2分钟版)

字符串常量池底层是一张哈希表叫 StringTable,存的是字符串对象的引用。它的位置有个重要演进:JDK 6 在永久代,JDK 7 开始移到了堆里,这样做的好处是 intern 的字符串能被正常垃圾回收,也避免了永久代 OOM。区分字面量和 new:字面量比如直接写 “hello”,编译期就放进常量池并且复用,所以两个相同字面量用 == 比较是 true;而 new String(“hello”) 是运行期在堆上新建对象,不进池,和字面量 == 比较是 false。经典题 new String(“a”) 创建几个对象,答案是最多两个,一个堆对象,一个常量池里字面量对象,如果池里已经有了就只创建堆这一个。intern 方法在 JDK 7 之后逻辑变了:因为池在堆里了,intern 不再复制字符串,而是直接把堆对象的引用登记进池,所以用 new String 拼接出来的字符串调 intern,再和后面的字面量比较可以是 true。最后是拼接优化,纯字面量拼接编译期就合并成一个字面量,含变量的拼接 JDK 8 用 StringBuilder、JDK 9 之后用 invokedynamic,只有循环里拼接才需要我们手动用 StringBuilder。

追问与易错

追问方向:

  • “常量池为什么从永久代移到堆?”→ 永久代空间有限易 OOM,且回收时机依赖 Full GC;移到堆后字符串可随堆正常回收,并能调大容量
  • “new String(“a”) 创建几个对象?”→ 最多两个:堆中 new 的对象 + 常量池中字面量对象(池中已存在则只一个)
  • “JDK 6 和 JDK 7 的 intern 区别?”→ JDK6 复制字符串到永久代池返回副本;JDK7+ 池在堆中,直接登记堆对象引用,不复制
  • “StringTable 太小会怎样?”→ 哈希冲突加剧,intern 退化为链表查找变慢,可用 -XX:StringTableSize 调大
  • “String s = “a”+“b” 和 String s = a+b(a,b为变量)区别?”→ 前者编译期合并入池,后者运行期 StringBuilder 拼接生成新堆对象不在池

易错点:

  • ❌ “常量池在方法区/永久代”——JDK 7 起已在堆中
  • ❌ “intern 总是复制字符串”——JDK 7+ 只登记引用不复制
  • ❌ “字符串拼接都慢”——单次拼接被编译优化,仅循环拼接需手动 StringBuilder