【Java】字符串常量池深度解析:从面试题到内存原理
一、开篇灵魂拷问:new String("云扬") 到底创建了几个对象?
相信大家面试时都遇到过这个经典问题 ——一行 new String("云扬") 会创建几个对象?答案是 2 个! 先上代码实例,再拆解原理:
// 关键代码示例 1:new String 创建对象分析
public class StringPoolDemo {
public static void main(String[] args) {
// 这行代码触发两个对象创建
String s = new String("云扬");
}
}
内存模型拆解(结合 JVM 内存结构):
- 字符串常量池中的对象:JVM 先检查常量池是否存在 “云扬”,不存在则先在常量池创建一个字符串对象;
- 堆内存中的对象:
new String()关键字强制在堆内存中创建一个新的字符串对象,内容与常量池中的一致; - 栈上的引用:变量
s存储在栈中,指向堆内存中的字符串对象。
补充:如果常量池已存在 “云扬”,则仅在堆中创建 1 个对象,但题目默认 “首次创建” 场景,故答案为 2 个。
二、字符串常量池的核心作用:复用 + 省内存
为什么 Java 要设计字符串常量池?因为字符串是开发中最常用的数据类型,复用已有对象可减少内存开销和 GC 压力。对比两种创建方式的差异:
// 关键代码示例 2:两种创建方式对比
public class StringCreateDemo {
public static void main(String[] args) {
// 方式 1:双引号直接创建(优先复用常量池对象)
String s1 = "三妹";
String s2 = "三妹";
System.out.println(s1 == s2); // true(指向同一个常量池对象)
// 方式 2:new 关键字创建(强制堆内存新建)
String s3 = new String("云扬");
String s4 = new String("云扬");
System.out.println(s3 == s4); // false(堆中两个不同对象)
}
}
核心结论:
- 双引号创建:优先查询常量池,存在则直接返回引用,不存在则创建后返回(仅 1 个对象);
- new 关键字创建:无论常量池是否存在,都会在堆中新建对象(最少 1 个,最多 2 个)。
三、字符串常量池的内存位置演变(JDK 版本差异)
| JDK 版本 | 常量池位置 | 核心说明 |
|---|---|---|
| JDK 6 及以前 | 永久代(PermGen) | 永久代是堆的子区域,大小固定,易触发 OOM |
| JDK 7 | 堆内存(Heap) | 解决永久代空间不足问题,动态扩容 |
| JDK 8 及以后 | 元空间(Metaspace) | 元空间使用本地内存,不受 JVM 堆大小限制,彻底解决 OOM |
关键补充:永久代 vs 元空间
很多同学会混淆这三个概念,用通俗的比喻解释:
- 方法区:JVM 规范中的 “接口”,定义了静态数据的存储规范;
- 永久代:HotSpot 虚拟机对方法区的 “实现类”(JDK 8 前),存于堆中;
- 元空间:JDK 8 后替代永久代的 “实现类”,存于本地内存,动态扩容。
四、实战避坑:字符串常量池的常见误区
误区 1:String.intern() 方法的作用
// 关键代码示例 3:intern() 方法实战
public class InternDemo {
public static void main(String[] args) {
// 字面量"云扬"在类加载时已入池,new String()在堆中创建新对象
String s1 = new String("云扬");
// 池中已有"云扬"(来自字面量),intern()返回池中引用(字面量对象)
String s2 = s1.intern();
// 直接使用字面量,返回池中同一引用
String s3 = "云扬";
// s1是堆中新对象,s2是池中字面量对象,二者不同
System.out.println(s1 == s2); // false
// s2和s3都指向池中同一对象
System.out.println(s2 == s3); // true
}
}
注意:JDK 7 后
intern()不再复制对象到常量池,而是存储堆对象的引用,进一步节省内存。
误区 2:字符串拼接的常量池行为
// 关键代码示例 4:字符串拼接分析
public class StringConcatDemo {
public static void main(String[] args) {
String a = "云";
String b = "扬";
String c = "云扬";
String d = a + b; // 编译期无法确定,堆中创建新对象
String e = "云" + "扬"; // 编译期优化为 "云扬",复用常量池对象
System.out.println(c == d); // false
System.out.println(c == e); // true
}
}
五、总结:字符串常量池的核心价值
- 性能优化:复用字符串对象,减少内存分配和 GC 次数;
- 面试重点:
new String创建对象数、常量池位置演变、intern()方法是高频考点; - 开发建议:优先使用双引号创建字符串,避免不必要的
new String,减少内存浪费。
你在开发中有没有踩过字符串常量池的坑?或者遇到过其他相关的面试题?欢迎在评论区交流~



