☰
String str = new String(“abc“)创建几个对象?讲透字符串常量池与JVM内存
2026/9/26 18:03:58 网站建设 项目流程

面试圈里有一道经久不衰的送分题,也是送命题:String str = new String("abc")创建了几个对象?我面过不少候选人,从刚毕业的应届生到号称三年经验的开发,十个人里能把这个答案讲清楚的,撑死两三个。大多数人憋出一句"两个对象",然后面试官再追问一句"为什么是两个?在哪两个地方?"就卡壳了。这道题之所以经典,是因为它背后串联着String 常量池、编译期优化、堆内存分配、引用指向关系四个核心知识点,一道题就能把一个人的 Java 基本功摸得七七八八。这篇博文我就从面试官的角度,把这道题掰开揉碎了讲一遍,顺便把面试官接下来会追问的一连串String问题也一并拆给你看。无论你是准备面试还是单纯想把基础补扎实,这篇文章都值得你耐心读完。

1. 这道面试题到底在考什么

先别急着背诵答案。你要明白面试官抛出这道题时,他嘴上问的是"创建了几个对象",脑子里真正想确认的是下面这五件事:

  • 你知不知道字符串对象和普通对象在内存分配上的差别。
  • 你理不理解 Java 的常量池机制,尤其是字符串常量池的位置和生命周期。
  • 你能不能分清楚编译期和运行期两件完全不同的事情。
  • 你知不知道new关键字一定会触发堆上对象的创建,而字面量赋值不一定。
  • 你能不能把"创建了几个对象"和"栈上的引用指向谁"这两件事分开说清楚。

很多候选人只知道背结论:"创建了两个对象。"但实际上,这个答案只在一种情况下成立。如果你的条件变了,答案也会跟着变。这就是为什么我强调,学这道题不要背,要理解它背后的机制。

1.1 为什么面试官偏爱字符串问题

字符串在 Java 里几乎是所有业务代码里出现频率最高的对象类型。日志、参数传递、JSON序列化、SQL拼接,哪样离得开 String?正因为太常用了,很多开发反而忽略了对 String 底层机制的深究。面试官问字符串,成本低、区分度高,一个问题就能知道你是"用过"还是"理解过"。

我自己的经验是,能清晰回答这道题的人,通常对 JVM 内存模型也有一定了解,因为字符串常量池从 JDK 7 开始从方法区挪到了堆中,这个细节会直接影响你对"对象创建了几个"的判断。反过来,对这块一知半解的人,后面追问intern()、字符串拼接优化,基本也是答不上来的。

1.2 一个答案在不同条件下会变

这是我给所有准备面试的人的第一个提醒:String str = new String("abc")这道题没有一个恒定不变的答案。它的完整回答是:

  • 如果字符串常量池中还没有"abc"这个字符串,那会创建两个对象:一个在字符串常量池中(编译期创建),一个在堆上(运行期new创建)。
  • 如果字符串常量池中已经存在"abc",那只会创建一个对象,也就是new出来的堆上对象,常量池里那个是直接复用。

这个"看情况"的答案,才是面试官真正想听到的完整版。你上来直接说"两个",说明你背过答案但没理解机制;你把上述两种情况都讲清楚,再补一句引用指向,这题才算答到位了。

2. 字符串常量池:理解这道题的第一块拼图

要理解这道题,你首先得搞清楚字符串常量池到底是个什么东西。很多人对它的理解停留在"一块存放字符串的内存区域",这没错,但太模糊了。我尽量讲得接地气一点。

2.1 常量池里的字符串到底怎么来的

Java 源码文件经过编译后会生成 class 文件,class 文件里有一个**常量池(Constant Pool)**区域,里面存放了类、方法、字段、字符串字面量等符号信息。也就是说,你在代码里写的每一个字符串字面量,比如"abc",在编译期就已经被记录在 class 文件的常量池中了。

当 JVM 加载这个 class 文件时,这些字符串字面量会被解析并放入运行时常量池,进而存放到 JVM 的**字符串常量池(String Pool)**里。所以记住一个关键点:字面量字符串是编译期就确定下来的,它在类加载阶段就会进入字符串常量池。

你可能会问:字符串常量池到底在哪里?这个问题的答案在不同 JDK 版本里不一样。JDK 6 及之前,字符串常量池放在方法区(永久代)里;JDK 7 开始,它被移到了 Java 堆中。这也是为什么有些老面试题会说"常量池在方法区",放在今天这个说法已经不准确了。如果你面试时能顺带提一句这个 JDK 版本的变化,面试官对你的印象分会明显不一样。

2.2 字符串为何特殊:不可变与复用

String 类被设计成不可变对象,也就是对象一旦创建,它的值就不能被修改。这种设计带来的一个好处是安全性高,多个引用可以放心共享同一个字符串对象而不用担心被篡改;另一个好处就是为字符串池化复用创造了条件。

你可以把字符串常量池想象成一个"公共仓库":所有用字面量方式创建的字符串都会先到这个仓库里找,如果仓库里已经有了相同内容的字符串,就直接返回引用,不再重复创建。这就是为什么下面这段代码打印的结果是true:

String s1 = "abc"; String s2 = "abc"; System.out.println(s1 == s2); // true

s1和s2指向的是字符序列完全相同的同一个字符串常量池对象,==比较的是引用地址,地址相同所以是true。这跟equals比较内容根本不是一回事,很多新手会把它们搞混。

2.3 基本类型包装类也有池,别搞混

顺着这个思路,面试官还经常会考一道变种题:Integer a = 127; Integer b = 127; a == b结果是true,而Integer c = 128; Integer d = 128; c == d结果是false。这就是基本类型包装类的缓存机制,Integer默认缓存了-128到127之间的值,超出这个范围就是两个不同的对象了。它跟字符串常量池不是一回事,但考察的都是同一个底层能力:你是不是真的理解对象在内存中怎么分配和复用。能把这两道题串联起来回答的人,基础一定不会差。

3. String str = new String("abc") 逐层拆解

现在正式拆这道题。你要把一整行代码拆成编译期和运行期两半来看,答案自然就浮出水面了。

3.1 编译期的"abc"发生了什么

你在代码中写下的"abc"是字符串字面量,编译成 class 文件后,它作为常量池中的一个CONSTANT_String_info条目存在。JVM 加载类时,会把这个字面量放进字符串常量池。这一步发生在编译期和类加载阶段,跟你有没有写new没有任何关系。

这里要特别注意一个点:只要你的代码里写了"abc"这个字面量,哪怕你只写了一行String x = "abc",它也会在类加载时进入常量池。所以面试官如果追问"new String("abc")里那个"abc"是不是也需要创建对象",答案是:它本来就会以字面量的形式进入常量池,跟你new不new没关系。

3.2 运行期 new String 又发生了什么

接下来看new String("abc")这一部分。new关键字在运行期的语义非常明确:在堆上分配一块内存,创建一个全新的 String 对象。这个对象的内容虽然也是"abc",但它和常量池里的那个"abc"是两个完全独立的对象,只是内容恰好一样。

new出来的这个 String 对象,它的内部字符数组(char[],JDK 9 之后是byte[])会被赋值为常量池中那个字符串的内容。所以准确地说,new String("abc")是基于常量池中已有的字符串内容,在堆上再拷贝创建了一个新对象。

3.3 最终答案:创建了几个对象

把编译期和运行期合在一起看:

  • 如果常量池中没有"abc":编译期/类加载阶段创建 1 个常量池对象,运行期new创建 1 个堆对象。共 2 个对象。
  • 如果常量池中已有"abc":常量池对象已存在无需重复创建,运行期new创建 1 个堆对象。共 1 个对象。

最后,str这个引用变量指向哪里?它指向的是堆上new出来的那个对象,不是常量池里的那个。这一点也经常被面试官追问,你要一口答出来:str的引用指向的是堆内存中的 String 对象。

为了帮你彻底看清这个过程,我用一段代码做个对比演示:

String str1 = new String("abc"); // 假设常量池没有"abc" String str2 = "abc"; // 直接复用常量池对象 String str3 = str1; // 指向堆上同一个对象 System.out.println(str1 == str2); // false,堆对象 vs 常量池对象 System.out.println(str1 == str3); // true,同一个堆对象 System.out.println(str2 == "abc"); // true,两个字面量都指向常量池同一个对象

str1 == str2是false,这个结果是全题的核心所在:它证明了new出来的字符串对象和常量池中的对象不是同一个东西。很多候选人能说出"创建两个对象",但解释不了为什么str1 == str2是false,这就是理解还差最后一层窗户纸。

3.4 面试官的高频追加提问

如果这题你答得漂亮,面试官通常会追加下面这几个问题,我帮你提前演练一下:

  • 问:String s = "ab" + "cd"创建了几个对象?答:"ab"和"cd"分别是两个常量池对象,但"ab" + "cd"这个表达式在编译期就会被优化成"abcd"这个单一字面量,所以编译期创建了"ab"、"cd"、"abcd"三个常量池对象(假设之前均不存在),运行期没有new,不产生堆对象。如果面试官问的是"创建了几个对象",这个问题反而比new String("abc")更阴险,因为常量折叠会导致"ab" + "cd"和"abcd"是同一个对象。

  • 问:String s = new String("ab") + new String("cd")创建了几个对象?答:这题比上一题复杂得多。"ab"和"cd"两个字面量在常量池中创建(假设不存在),两个new在堆上创建两个 String 对象,new String("ab") + new String("cd")底层是通过StringBuilder完成的,StringBuilder在toString()时会new一个 String 对象。所以至少创建了 5 个对象:常量池 2 个,两个new出来的 String 各 1 个,拼接结果 1 个。当然StringBuilder这个临时对象自己也算一个,就看面试官怎么定义"对象"的范围了。这种题考察的就是你对字符串拼接底层实现的掌握程度,一般问到这里,候选人基本就招架不住了。

  • 问:String s = "abc"; s = s + "def";这行代码创建了几个对象?答:同样要看常量池情况。"abc"已在常量池(假设),运行时s + "def"会创建一个StringBuilder,追加"def"后toString()生成新的 String 对象。所以运行期至少创建了 StringBuilder 和新 String 两个对象。这题的核心考点是:字符串常量拼接在编译期完成,字符串变量拼接在运行期通过 StringBuilder 完成。

4. 从这道题延伸出去的 String 全家桶考点

面试官问完创建个数之后,话题往往会顺势滑向 String 的兄弟类和方法细节。这些都是我在面试中真实问过、也是后来者最容易被问懵的地方,索性一次讲清楚。

4.1 String、StringBuffer、StringBuilder 三兄弟怎么选

很多候选人能把三者的区别背得滚瓜烂熟:String 不可变,StringBuffer 线程安全,StringBuilder 线程不安全。但你再追问一句"那 StringBuffer 的线程安全是怎么实现的",一半人就愣住了。

StringBuffer的核心方法是加了synchronized关键字的,所以多线程环境下对同一个 StringBuffer 实例的修改是串行化的,这就是它线程安全的由来。StringBuilder没有加锁,所以在单线程下性能更高,因为没有同步开销。日常开发中,方法内局部变量的字符串拼接用 StringBuilder 足够,涉及多线程共享可变字符串才用 StringBuffer。

我见过太多因为这里概念不清而翻车的情况。有个候选人答得特别逗:"StringBuffer 是线程安全的,所以我们应该优先用 StringBuffer。"这个说法放在单线程场景完全是多余的,你把锁的开销白白背在身上,图什么呢?面试官想听到的是你有场景意识,而不是死记概念。

关于"StringBuffer 转换为 String",我顺便多说一句,这是很常见的实际需求。StringBuffer有个toString()方法,直接调用就能得到 String:

StringBuffer buffer = new StringBuffer("hello"); String result = buffer.toString();

底层实现上,toString()会基于当前 StringBuffer 内部的字符数组创建一个新的 String 对象。同理,StringBuilder也是这么转的。我在项目里经常看到有人用buffer.toString()之后忘了接收返回值,然后奇怪为什么 buffer 没有变成 String——因为 toString 返回的是新对象,不是把原对象改造成 String,StringBuffer 永远是 StringBuffer。

4.2 String 类的常用方法,面试常考这几个

接下来盘点一下 String 类的高频方法。这些不是面试中直接考创建对象,而是在写代码的环节会用到的,面试官也常会假装不经意地问一句:

  • length():返回字符串长度。注意是length()方法,不是length属性,数组才有length属性,新手最容易在这两个概念上犯迷糊。
  • charAt(int index):返回指定位置字符。面试中经常配合字符串反转、回文判断这类手写算法出现。
  • substring(int beginIndex, int endIndex):截取子串。JDK 7 之后底层是新建字符数组,不再共享原字符串的char[],这也是一个经典的内存优化考点。
  • equals()与equalsIgnoreCase():比较字符串内容是否相同。任何两个字符串对象之间比较内容都该用equals,==永远是在比较引用地址。
  • indexOf()/lastIndexOf():查找字符或子串首次/最后一次出现的索引。
  • replace()/replaceAll():替换字符或正则匹配的子串。
  • split(String regex):按正则表达式拆分字符串。这里有个大坑:split(".")是拆不开的,因为.在正则里是任意字符,必须写成split("\\.")。
  • trim()/strip():去掉首尾空格。JDK 11 引入的strip()不仅能去除半角空格,还能去除全角空格等 Unicode 空白字符,比trim()更彻底。
  • intern():这个方法单独拎出来说,它能把堆上的字符串对象手动放入常量池并返回常量池中的引用。

我见过一个很典型的面试场景:面试官让候选人写一个方法统计字符串中每个字符出现的次数。候选人用split循环切,写出来的代码又慢又绕;另一个候选人直接用 HashMap 遍历charAt(),几行就搞定了。这就是基础方法熟练度的差距,你 API 用得不熟,连简单需求都写不利索。

4.3 intern() 方法的正确打开方式

intern()值得单独一节来讲,因为它几乎是new String("abc")这道题的"题眼"。很多候选人不知道intern()是干嘛的,但面试官只要问到了机制层面,intern()基本必聊。

intern()的行为是这样的:当你在堆上的 String 对象上调用intern()时,JVM 会检查字符串常量池里有没有相同内容的字符串。如果有,直接返回常量池中的引用;如果没有,把这个字符串的内容复制到常量池中(JDK 7 及之后,可以在常量池中记录堆中对象的引用,并不一定要复制整个对象),然后返回常量池中的引用。

来看一段经典代码:

String s1 = new String("abc"); String s2 = s1.intern(); String s3 = "abc"; System.out.println(s1 == s3); // false System.out.println(s2 == s3); // true,intern()返回的是常量池引用

s1是堆对象,s3是常量池对象,所以比较是false。s2是intern()的返回值,指向常量池中的同一个对象,跟s3是true。

我实际项目中用过intern()的一次经历是做大量重复字符串的去重缓存。当时有个接第三方数据的服务,单条数据里有一个状态字段,取值就五六个固定值,但每天要处理几百万条记录。如果不做处理,每条记录都会产生一个新的 String 对象,堆内存压力很大。后来在写入缓存之前对这个字段调了intern(),让相同内容的字符串复用同一个常量池对象,内存占用肉眼可见地降了下来。

不过这里我必须给个忠告:intern()不是万能的,滥用它反而会搞垮你的 JVM。JDK 7 之后常量池在堆里,如果无限制地把运行时产生的大量不同字符串intern()进去,常量池会越撑越大,最终可能触发堆溢出。所以intern()只适合用在"有限集合、高重复率"的字符串上,比如状态值、枚举值、字典项,而不适合用户输入类的内容。我自己做技术方案时,会先估算字符串的基数,基数超过几千就放弃intern()方案,改用自定义的弱引用缓存,这样既能复用对象,又不会被常量池长期占住内存。

5. 真实面试场景与避坑清单

这一节我想直接模拟一遍真实面试现场,把常见的对话节奏和回答话术写出来,顺便盘点一下我在面试官视角看到的高频错误。

5.1 典型追问链与参考回答话术

我一般会按下面这条链路追问:

Q1:String str = new String("abc") 创建了几个对象? Q2:你刚才说常量池和堆,这两个对象分别在什么时候创建的? Q3:str 这个引用指向的是哪个对象? Q4:如果是 String s = "abc",创建了几个对象? Q5:String s = "a" + "b" + "c" 创建了几个对象? Q6:你平时代码里大量拼接字符串用的是什么? Q7:StringBuilder 和 StringBuffer 有什么区别?

完整的回答话术我给你整理了一段,面试时可以照着这个思路组织语言:

这行代码创建的对象数量取决于"abc"这个字面量是否已经存在于字符串常量池中。如果不存在,会创建两个对象:一个是编译期生成的、类加载时放入字符串常量池的对象;另一个是运行期 new 关键字在堆上创建的对象,str 引用指向堆上的这个对象。如果常量池里已经有"abc",那就只创建一个堆对象,常量池中的对象直接复用。String 的赋值和 new 的区别在于,字面量赋值会去常量池中寻找或创建对象,而 new 无论如何都会在堆上创建一个新对象。

这段话如果你能不打磕绊地说完整,这道题在面试官那里至少是个"良"以上的评价。

5.2 我在面试官视角看过的翻车现场

我总结了一些高频翻车点,帮你提前避坑。

第一个翻车点:直接答"两个对象"。这个答案本身在特定条件下没错,但暴露了你没有考虑常量池里可能已有"abc"的情况。面试官听到这个答案后通常会追问一句"一定有两个吗?",然后等你挣扎。你要做的就是在第一遍回答时就把两个情况都讲清楚,而不是等追问再补。

第二个翻车点:分不清编译期和运行期。有人会说"创建了三个对象,abc 一个,new 一个,str 一个",这就把引用变量也算成对象了,属于概念混淆。引用变量 str 是在栈上分配的引用,不是对象本身,它只是"指向"对象的地址。

第三个翻车点:答非所问扯到常量折叠。候选人听到new String("abc")之后,上来就背一段"JVM 会先解析常量池,然后用 StringBuilder 拼接"之类的答案,完全偏离了题目。这道题里根本没有拼接,你把题目听错了。

第四个翻车点:不知道 JDK 版本影响了常量池位置。候选人答完"对象在方法区"之后,我追问一句"那 JDK 7 之后呢?"就沉默的,不在少数。基础知识点必须落到版本的变化线上,才能证明你是真的在关注这门语言的发展,而不是背了一本有年代感的老旧面经。

5.3 面试之外的工程启示:别在生产代码里放纵 new String

把话题拉回工程实践。这道题给我们的最大启示其实不在面试里,而在日常编码习惯上。

我 review 同事代码时,偶尔会看到有人写String s = new String("固定值")。这种写法除了多创建一个堆对象之外没有任何价值,纯属浪费内存。正确的写法就是直接String s = "固定值",让 JVM 走常量池复用。虽然单个对象的内存开销微乎其微,但如果是高频执行路径里的垃圾生成,GC 压力就会积少成多。

另外,循环里拼接字符串是最常见的性能杀手。看这段代码:

String result = ""; for (int i = 0; i < 1000; i++) { result = result + i + ","; }

每次循环都会创建一个新的 StringBuilder、调用两次 append,最后toString()生成新 String。1000 次循环就是 1000 个中间 String 对象,GC 忙得团团转。正确的写法是:

StringBuilder sb = new StringBuilder(4096); for (int i = 0; i < 1000; i++) { sb.append(i).append(','); } String result = sb.toString();

我还会建议你在创建 StringBuilder 时尽量预估初始容量。如果你大概知道结果的长度,直接new StringBuilder(长度),可以减少扩容次数。StringBuilder 内部默认容量是 16,超过 16 会自动扩容,扩容涉及数组复制,这也是开销。

字节码层面,+号拼接字符串时,javac 编译器会自动把代码重写成 StringBuilder 的 append 调用。所以我前面说"字符串变量拼接在运行期通过 StringBuilder 完成",不是类比,而是编译器的真实行为,你用javap -c反编译一下就能看到。

5.4 最后分享一个我常用的记忆技巧

这道题我讲给带过的每一位新人,后来总结成一个傻瓜式记忆口诀,分享给你:

字面量进池,new 必上堆;编译期管家,运行期堆内;引用指谁,看你在哪种场合。

展开说一下:字面量字符串编译期就进了常量池;new一定在堆上创建新对象;常量池创建属于编译期和类加载阶段的事,堆对象创建属于运行期;引用变量指向谁要看具体代码,new String("abc")的引用指向堆对象,"abc"字面量赋值的引用指向常量池对象。

这个口诀不是让你背答案,而是帮你快速建立画面感。每当你在面试中碰到类似的字符串问题,先在脑海里画出"常量池"和"堆"两个区域,再往里面放对象,回答立刻有了层次。我试过用这个方法给不少准入职的同事做过模拟面试,通过率确实高了不少。

回到最开始那道题,你现在应该能给出一个让面试官满意的完整答案了。不过比答案更重要的是,你借这道题把 JVM 内存分配、编译期运行期分工、String 不可变设计、字符串拼接底层实现这些知识点串成了一条线。面试问到这儿,你已经不是在应付问题了,而是在跟面试官平等地聊技术。这才是这道题真正想让你练成的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询