高 进阶
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 指向堆对象)java3. 经典追问: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); // truejava- 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