String str="abc" 和 new String("abc") 核心区别+内存存储详解(JDK7+)

先明确核心结论首次执行时堆中会生成两个不同的String对象;非首次执行(常量池已有"abc"引用)时,堆中仅生成1个新的String对象。二者的核心差异体现在栈引用指向、堆对象创建规则、内存复用性三个维度,以下结合内存存储细节、对象数量逐一拆解,所有分析基于目前主流的JDK7+版本(常量池位于堆区,存储字符串对象引用)。

一、String str = "abc":字面量创建,堆中仅1个对象+常量池复用

这是JVM推荐的字符串创建方式,核心遵循常量池唯一引用机制,全程堆中仅创建1个字符串对象,具体内存存储流程:

  1. JVM首先检查堆区的字符串常量池(String Table),判断是否存在"abc"的字符串引用;
  2. 首次执行时,常量池无对应引用,JVM在堆区的普通内存区域创建1个真正的"abc"字符串对象
  3. 将堆中这个"abc"对象的引用地址存入字符串常量池(哈希表结构,实现引用唯一);
  4. 栈区的引用变量str直接指向字符串常量池中的"abc"引用,最终间接指向堆中的唯一"abc"对象。

核心特点:堆中仅1个对象,所有后续声明String str2="abc"的栈引用,都会直接复用常量池中的引用,不会在堆中新建任何对象,实现内存最大化复用。

二、new String("abc"):构造方法创建,堆中至少1个、最多2个对象

这是强制在堆中新建对象的方式,栈引用永远不指向常量池,直接指向堆中新对象,具体内存存储流程(分两种场景):

场景1:首次执行(常量池无"abc"引用)→ 堆中2个独立String对象

这是你问题中“堆中是否是两个对象”的核心场景,步骤如下:

  1. JVM先检查字符串常量池,无"abc"引用,触发字面量创建的默认流程:在堆区创建第1个"abc"字符串对象,并将其引用存入常量池;
  2. 执行new String("abc"),JVM在堆区的另一块内存区域,创建第2个全新的String对象(该对象与第1个对象共享底层的字符数组(char[]/byte[]),但二者是堆中独立的对象,内存地址不同);
  3. 栈区的引用变量str直接指向堆中这个新创建的第2个String对象,与常量池无直接关联。

场景2:非首次执行(常量池已有"abc"引用)→ 堆中仅1个新String对象

常量池已有"abc"的引用(堆中已有第1个"abc"对象),JVM会跳过字面量创建流程,直接执行new String("abc"),在堆区新建1个独立的String对象,栈引用直接指向该新对象。

核心特点:无论常量池是否有对应引用,堆中必然会新建1个String对象;仅当常量池无引用时,会因“兜底创建字面量对象”导致堆中出现2个对象,且两个对象始终是堆中独立实例。

三、二者核心区别对比表(JDK7+)

为了更清晰区分,从栈引用指向、堆对象数量、内存复用性、==判断结果四个核心维度总结,对比场景为首次执行(常量池无"abc")

特性 String str = "abc"(字面量) new String("abc")(构造方法)
栈引用指向 指向字符串常量池的"abc"引用 直接指向堆区的新String对象
堆中对象数量 仅1个 2个(独立实例,共享底层字符数组)
内存复用性 极致复用,后续创建直接复用常量池引用 无复用,每次new都在堆中新建对象,内存冗余
与字面量==判断 str == "abc" → true(均指向常量池引用) str == "abc" → false(一个指向堆,一个指向常量池)
底层字符数组 独用(堆中仅1个对象) 与常量池对应的堆对象共享

四、关键补充:易混淆的核心细节

1. 堆中两个对象的“共享”与“独立”

new String("abc")创建的新对象,与常量池对应的堆对象,仅共享底层存储字符的数组(char[]/byte[]),但String对象本身是完全独立的:二者的hashCode()(重写后与字符内容一致)相同,但==判断为false(内存地址不同),equals判断为true(内容相同)。

2. 为何JVM不推荐使用new String("abc")?

唯一的问题是内存冗余:堆中出现重复的String对象,且栈引用无法复用常量池,若大量使用会导致堆内存中充斥重复字符串对象,增加GC压力,甚至可能引发OOM,仅在极少数需要“独立字符串对象”的场景才会使用。

3. 如何让new String("abc")的对象实现常量池复用?

可通过手动调用intern()方法,将堆中新对象的引用存入常量池,实现复用:

String str = new String("abc").intern(); // 调用intern后,栈引用指向常量池引用
String str2 = "abc";
System.out.println(str == str2); // true:二者均指向常量池同一引用

五、极简内存模型示意图(首次执行场景)

用简单的层级关系展示三者(栈、常量池、堆)的关联,直观理解存储差异:

1. String str = "abc" 的内存模型

栈区:str → 常量池区:"abc"引用 → 堆区:唯一的"abc"字符串对象(char[]={'a','b','c'})

2. new String("abc") 的内存模型

栈区:str → 堆区:新String对象(共享char[])
常量池区:"abc"引用 → 堆区:原"abc"字符串对象(char[]={'a','b','c'})

(堆中两个String对象共享同一个char[],但对象本身是独立的两个内存地址)

总结

  1. 首次执行时,堆中是两个独立的String对象(字面量的原对象+new的新对象),非首次执行时堆中仅1个new的新对象;
  2. 字面量创建的栈引用指向常量池,new创建的栈引用直接指向堆,这是二者最核心的区别;
  3. 字面量方式天然实现内存复用,new方式必然创建堆新对象,无复用性,日常开发优先使用字面量创建字符串;
  4. 堆中所有字符串对象的本体始终唯一存储在堆区,常量池仅存储堆对象的引用(JDK7+),不会存储对象本身。
posted @ 2026-01-29 10:56  先弓  阅读(0)  评论(0)    收藏  举报