1. 面试题背后的真实考点
这道题我印象太深了。早年在京东面试候选人,我自己就挂在上面过;后来坐在面试官的位置上,也用它筛过不少简历上写着“精通Java”的候选人。String str = new String("abc") 这句话,表面上在问“创建了几个对象”,实际上考察的是三块硬功夫:字符串常量池的机制、new 关键字创建对象的流程、以及JVM内存区域的基础认知。这三块知识,几乎覆盖了Java初级到中级面试的核心内容。
很多候选人一听到“创建了几个对象”,条件反射就想答“两个”,但追问下去就露馅了:一个是字符串常量池里的 "abc",一个是堆上的 String 实例。这个答案背过的人不少,但真正能讲清楚“为什么是池里一个、堆上一个”“两个对象分别在什么时候被创建”“如果池里已经有 "abc" 了还会创建几个”“s.intern() 在其中扮演什么角色”的人,其实并不多。
这道题的实际杀伤力不在于答案本身,而在于它能一层层往下挖,直到挖出候选人知识体系的真实深度。我见过有人把这个面试题背得滚瓜烂熟,却在问到“那 String s = "abc" 和 String s = new String("abc") 在字节码层面有什么区别”时彻底卡壳。所以这篇文章我不打算只给答案,而是把整条知识链完整地拆开,从内存模型讲到字节码指令,再从常量池讲到 intern() 的坑,最后给你一套可以直接拿去用的面试回答话术。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先说结论:一个还是两个,取决于前提条件
注意:这个问题的标准答案不是“一个”也不是“两个”,而是“要看字符串常量池里是否已经存在
"abc"这个字面量”。
具体分两种情况:
- 第一次执行
String str = new String("abc"),且常量池中没有"abc":会在字符串常量池中创建一个"abc"对象,同时在堆上创建一个 String 实例,一共 2个对象。 - 常量池中已有
"abc"(比如之前执行过String s = "abc"):只在堆上创建一个 String 实例,一共 1个对象。
这个“一个还是两个”的差异,恰恰是面试官最想听到的。候选人如果脱口而出一口咬定是“两个”,说明他背过答案但没理解机制;如果只说“一个”,说明他对常量池完全没有概念。只有能分情况讨论的人,才真正掌握了背后的原理。
但注意,这里的“2个对象”还有一个隐藏细节:如果 JVM 开启了指纹技术(即字符串常量池中 "abc" 和 String 实例的内部 value 数组引用的是同一份底层 char[] 数据),那严格来说底层字符数组只有一个。只是这个细节对于面试回答来说属于加分项,不属于必答项,后面我会详细展开。
3. 必须搞懂的两个基础概念
3.1 字符串常量池(String Constant Pool)到底是什么
字符串常量池是 JVM 为了减少字符串对象的重复创建而设计的一块内存区域。它的核心逻辑很简单:字符串是不可变对象(这是 String 类被 final 修饰的原因),既然不可变,那么同样的内容就没必要存多份。
在 JDK 1.7 之前,运行时常量池(包含字符串常量池)位于方法区(PermGen 永久代)中;JDK 1.7 开始,字符串常量池被移到了堆中。这个变化本身就是一个高频考点,面试官可能会追问“为什么要从方法区移到堆里”,答案是:永久代空间有限,字符串过多容易引发 OutOfMemoryError: PermGen space,移到堆后可以充分利用堆空间的垃圾回收能力,缓解这个问题。
还要注意区分几个容易被搞混的概念:
- Class 文件常量池:存在于
.class文件中的静态信息,包含类、方法、字段符号引用和字面量。 - 运行时常量池:JVM 加载 Class 文件后,将 Class 文件常量池中的内容加载到方法区(元空间)中形成。
- 字符串常量池:也常被称为 String Pool,JDK 1.7 之后被分配在堆中,用来存放字符串字面量。
加载一个类时,Class 文件常量池中的符号引用会被解析到运行时常量池,而字符串字面量则会被处理为 String 对象并放入字符串常量池。这个过程的细节,恰恰就是 new String("abc") 谜题的核心线索。
3.2 直接赋值与 new 的本质差异
要理解 String str = new String("abc") 做了什么,必须先拿它和 String str = "abc" 做对比。这两行代码的字节码指令完全不同,执行路径也大相径庭。
情况一:直接赋值 String str = "abc";
JVM 编译这段代码时,对应的是 ldc 指令(Load Constant)。执行 ldc 时,JVM 会先去字符串常量池查找是否已有内容为 "abc" 的对象:
- 如果池中已有
"abc",直接返回池中对象的引用,赋值给str; - 如果池中没有,就在池中创建一个新的
"abc"字符串对象,然后返回引用。
此时 str 指向的是常量池中的对象,堆上并没有额外的 String 实例。
情况二:new 创建 `String str
