基础篇三 一行 new String(“hello“) 到底创建了几个对象?90% 的人答错了

张开发
2026/4/19 23:40:29 15 分钟阅读

分享文章

基础篇三 一行 new String(“hello“) 到底创建了几个对象?90% 的人答错了
文章目录一、先说结论1 个或 2 个二、拆解过程这行代码到底做了什么第一步处理字面量 hello第二步执行 new String()三、用图理解内存布局场景一常量池中不存在 hello首次出现场景二常量池中已存在 hello四、什么情况下常量池会提前有 hello五、一个更复杂的变体六、intern() 方法手动入池intern 的经典面试题JDK 7七、JDK 6 vs JDK 7常量池的位置变化八、面试速答模板个人网站这道题看似简单实则是 Java 内存模型的试金石。很多面试者脱口而出两个但答案远没有这么简单——几个取决于上下文而能说清上下文的人才是真正理解 JVM 内存模型的人。一、先说结论1 个或 2 个StringsnewString(hello);如果字符串常量池中已经存在hello→ 创建1 个对象堆上的 String 实例如果字符串常量池中不存在hello→ 创建2 个对象常量池中的字面量 堆上的 String 实例为什么会有这种不确定性因为hello这个字面量在编译期就被确定了而它是否入池取决于在这行代码执行之前常量池中是否已经有了hello。二、拆解过程这行代码到底做了什么第一步处理字面量 “hello”当 JVM 遇到hello这个字面量时会先去字符串常量池中查找找到了→ 直接返回引用不做任何创建没找到→ 在常量池中创建一个hello字符串对象然后返回引用第二步执行 new String()new关键字一定会在堆内存上创建一个新的 String 对象用常量池中hello的值来初始化。StringsnewString(hello);用伪代码描述整个过程1. 查找常量池是否有 hello - 没有 → 常量池创建 hello 对象对象 A - 有 → 跳过 2. 堆上 new 一个 String 对象对象 B值为 hello 3. s 指向对象 B堆上的对象所以对象 A 是常量池中的对象 B 是堆上的它们是两个独立的对象只是值相同。三、用图理解内存布局场景一常量池中不存在 “hello”首次出现字符串常量池 堆 ┌──────────────┐ ┌──────────────┐ │ hello (A) │ │ hello (B) │ └──────────────┘ └──────────────┘ ↑ │ s 指向 B 创建了 2 个对象场景二常量池中已存在 “hello”字符串常量池 堆 ┌──────────────┐ ┌──────────────┐ │ hello (A) │ 之前已存在│ hello (B) │ └──────────────┘ └──────────────┘ ↑ │ s 指向 B 创建了 1 个对象四、什么情况下常量池会提前有 “hello”只要在此之前有代码触发了hello的入池常量池中就会存在// 情况 1直接使用字面量Stringahello;// hello 入池StringbnewString(hello);// 常量池已有只创建 1 个// 情况 2同一个类中前面的代码已经用过System.out.println(hello);// hello 入池StringsnewString(hello);// 常量池已有只创建 1 个// 情况 3intern() 方法StringcnewString(hel)newString(lo);c.intern();// hello 入池StringdnewString(hello);// 常量池已有只创建 1 个五、一个更复杂的变体StringsnewString(hello)newString(world);这行代码创建了几个对象逐步拆解步骤操作创建的对象1字面量hello入池常量池 “hello”如果不存在2new String(hello)堆上 String 对象3字面量world入池常量池 “world”如果不存在4new String(world)堆上 String 对象5拼接操作底层用 StringBuilder最终toString()创建堆上 String 对象最坏情况5 个对象2 个常量池 2 个 new 1 个拼接结果注意StringBuilder 本身也是对象但它是中间过程通常不计入这行代码创建了几个对象的答案中。六、intern() 方法手动入池intern()是 String 类的一个 native 方法作用是如果常量池中已存在该字符串返回常量池中的引用如果不存在将该字符串放入常量池并返回引用Strings1newString(hello);// 堆上对象Strings2s1.intern();// 返回常量池中的 helloStrings3hello;// 常量池中的 helloSystem.out.println(s1s2);// falses1 在堆s2 在常量池System.out.println(s2s3);// true都指向常量池中的同一个对象intern 的经典面试题JDK 7Strings1newString(a)newString(b);// 此时常量池中有 a 和 b但没有 ab// s1 指向堆上的 ab 对象s1.intern();// JDK 7常量池中存储的是堆上 ab 对象的引用不需要再拷贝一份Strings2ab;// ab 在常量池中已存在是 s1 的引用所以 s2 也指向同一个对象System.out.println(s1s2);// JDK 7 : true// JDK 6 : false常量池在永久代存的是拷贝七、JDK 6 vs JDK 7常量池的位置变化这是理解 String 内存模型的关键背景版本常量池位置intern() 行为JDK 6 及之前永久代PermGen把字符串复制一份到永久代JDK 7 及之后堆Heap把堆上对象的引用存入常量池这个变化直接导致了s1 s2在不同 JDK 版本下结果不同。JDK 7 以后常量池不再拷贝字符串内容而是直接记录引用避免了重复存储。八、面试速答模板Qnew String(“hello”) 创建了几个对象A1 或 2 个。如果常量池中已有 “hello”只在堆上创建 1 个 String 对象如果常量池中没有 “hello”会先在常量池创建 1 个再在堆上 new 1 个共 2 个。关键点是new一定会在堆上创建新对象而字面量是否入池取决于之前是否已经出现过。QString s new String(“a”) new String(“b”) 创建了几个对象A最多 5 个——常量池中 “a” 和 “b” 各 1 个如果不存在堆上new String(a)和new String(b)各 1 个拼接结果的堆上对象 1 个。拼接底层使用 StringBuilder 的toString()方法。Qintern() 方法的作用A将字符串放入常量池。如果常量池已有则返回常量池引用如果没有JDK 6 会复制字符串到永久代JDK 7 会记录堆上对象的引用。这也是为什么 JDK 7 中new String(a)new String(b)后调用 intern再用字面量赋值两个引用会指向同一个对象。相关文章原文阅读内容有帮助点赞、收藏、关注三连评论区等你

更多文章