面试考察点
- 基础概念掌握:面试官不仅仅是想让你回答一个数字,更是想考察你是否理解 Java 对象创建机制、字符串常量池的工作原理,以及字面量与
new关键字的区别。 - 分情况分析能力:这道题的答案取决于字符串常量池的当前状态,考察你是否有 “条件思维”,能否识别出不同的执行场景。
- JVM 内存模型理解:考察你是否清楚堆内存、字符串常量池的位置关系(JDK 6 vs JDK 7+ 的差异),以及对象在内存中的存储方式。
核心答案
这道题的答案 取决于字符串常量池中是否已存在 "abc":
| 场景 | 常量池状态 | 创建对象数 | 对象位置 |
|---|---|---|---|
| 首次执行 | 不存在 "abc" |
2 个 | 常量池 1 个 + 堆 1 个 |
| 非首次执行 | 已存在 "abc" |
1 个 | 仅堆中 1 个 |
一句话总结:new String("abc") 至少创建 1 个堆对象,最多创建 2 个对象(取决于常量池状态)。
深度解析
一、执行过程详解

上图展示了 String str = new String("abc") 的完整执行流程:
- 步骤 1(类加载阶段):JVM 首先解析字符串字面量
"abc",检查字符串常量池。如果常量池中不存在"abc",则创建一个 String 对象放入池中(对象①);如果已存在,则直接复用池中对象。 - 步骤 2(运行阶段):
new关键字触发堆内存分配,创建一个新的 String 对象(对象②)。这个对象的value数组引用会指向常量池中"abc"对应的字符数据。 - 步骤 3(赋值):变量
str指向堆中新创建的 String 对象,而不是常量池中的对象。
二、内存布局图解

上图清晰地展示了首次执行时的内存布局:
- 对象①(常量池中):由字符串字面量
"abc"触发创建,存储在字符串常量池中。这个对象可以被多个引用共享复用。 - 对象②(堆中):由
new String()显式创建,存储在普通堆区。这个对象是str变量独占的,不会被共享。 - 数据共享:虽然创建了两个 String 对象,但它们内部可能共享同一份字符数组数据(取决于 JDK 版本和具体实现),这就是为什么
str.equals("abc")返回true。
三、代码验证
public class StringObjectTest {
public static void main(String[] args) {
// 情况 1:首次执行,常量池无 "abc"
String s1 = new String("abc");
// 情况 2:非首次执行,常量池已有 "abc"
String s2 = new String("abc");
// 验证:s1 和 s2 是不同对象(都在堆中)
System.out.println(s1 == s2); // false
// 验证:s1 和常量池中的 "abc" 是不同对象
System.out.println(s1 == "abc"); // false
// 验证:字面量创建指向常量池
String s3 = "abc";
System.out.println(s3 == "abc"); // true
// 验证:内容相同
System.out.println(s1.equals(s3)); // true
}
}
代码执行分析:
- 第 1 行
new String("abc"):常量池无"abc",创建 2 个对象(常量池 1 + 堆 1) - 第 2 行
new String("abc"):常量池已有"abc",只创建 1 个对象(堆 1) - 第 3 行
"abc":直接复用常量池对象,0 个新对象
四、JDK 版本差异

上图对比了不同 JDK 版本中字符串常量池的位置差异:
- JDK 6 及之前:字符串常量池位于永久代,与堆内存分离。永久代空间固定且较小,大量字符串容易导致
java.lang.OutOfMemoryError: PermGen space。 - JDK 7 及之后:字符串常量池被移到 Java 堆中。堆空间通常更大且可动态扩展,GC 可以回收不再引用的字符串对象,大大减少了 OOM 风险。
- 对象创建数量不变:虽然常量池位置变了,但
new String("abc")创建对象的数量规则不变,仍然是"首次 2 个,非首次 1 个"。
五、最佳实践
避免不必要的 new String():
// ❌ 不推荐:浪费内存,创建多余对象
String s1 = new String("hello");
String s2 = new String("hello"); // 又创建一个堆对象
// ✅ 推荐:直接使用字面量,复用常量池对象
String s3 = "hello";
String s4 = "hello"; // 直接复用常量池中的对象,0 个新对象
// ✅ 特殊场景:确实需要独立对象时才使用 new
// 例如:需要独立 identity 的情况
何时需要 new String():
- 需要创建内容相同但引用不同的对象(极少见)
- 某些框架或库的特定需求
- 动态构建的字符串(但更推荐使用
StringBuilder)
面试高频追问
-
追问一:
String s = "a" + "b" + "c"创建了几个对象?在编译期,编译器会将
"a" + "b" + "c"优化为"abc",所以只会在常量池中创建 1 个对象。这是编译期常量折叠优化。 -
追问二:
String s = new String("a") + new String("b")创建了几个对象?会创建 6 个对象:
- 常量池:
"a"、"b"、"ab"(JDK 8 及之前可能不创建 “ab”)共 2-3 个 - 堆:
new String("a")、new String("b")、StringBuilder.toString()创建的 String 对象,共 3 个
- 常量池:
-
追问三:
String.intern()方法有什么用?intern()方法会将字符串对象加入常量池(如果池中不存在),并返回常量池中的引用。可以用于手动优化,减少重复字符串的内存占用:String s1 = new String("hello").intern(); String s2 = "hello"; System.out.println(s1 == s2); // true,都指向常量池 -
追问四:JDK 7 之后
intern()有什么变化?JDK 7 之前,
intern()会在永久代创建新对象;JDK 7 之后,如果常量池中没有该字符串,intern()会保存堆中对象的引用,而不是再创建一个新对象。
常见面试变体
- “
String s = new String("xyz")创建了几个 String Object?” - “
String s = "hello"和String s = new String("hello")有什么区别?” - “以下代码创建了几个对象?
String s1 = "a"; String s2 = "b"; String s3 = s1 + s2;”
记忆口诀
对象数量判断:先看池,再看堆。池中没有先入池(1 个),堆中必 new(1 个),首次 2 个非首次 1 个。
总结
String str = new String("abc") 创建对象的数量取决于常量池状态:首次执行创建 2 个对象(常量池 1 + 堆 1),非首次执行只创建 1 个对象(仅堆中)。核心是理解字符串字面量的常量池机制和 new 关键字的堆对象创建机制,生产环境应优先使用字面量方式避免不必要的对象创建。
原文链接: https://www.quanxiaoha.com/java-interview/string-new-abc-create-objects