
🌈 个人主页: Hygge_Code
🔥 热门专栏:从0开始学习Java | Linux学习 | 计算机网络
💫 个人格言: "既然选择了远方,便不顾风雨兼程"

文章目录
- [Java 字符串常量池](#Java 字符串常量池)
-
- 一、什么是字符串常量池?🤔
- [二、两种字符串创建方式 🐦🔥](#二、两种字符串创建方式 🐦🔥)
-
- [1. 字面量创建(自动入池)](#1. 字面量创建(自动入池))
- [2. new String() 创建(堆中创建,不入池)](#2. new String() 创建(堆中创建,不入池))
- [三、手动入池:String.intern() 🥝](#三、手动入池:String.intern() 🥝)
-
- [核心规则 👏](#核心规则 👏)
- 代码示例
- [四、JDK 版本关键差异](#四、JDK 版本关键差异)
-
- [JDK 1.7 为什么要将字符串常量池移动到堆中?](#JDK 1.7 为什么要将字符串常量池移动到堆中?)
- [五、易错场景 ⚠️](#五、易错场景 ⚠️)
-
- 坑1:字符串拼接的常量池行为
- [坑2:== 与 equals() 混用](#坑2:== 与 equals() 混用)
- [六、终极栗子 👏🌰👏](#六、终极栗子 👏🌰👏)
Java 字符串常量池
一、什么是字符串常量池?🤔
字符串常量池是 JVM 专门维护的一块共享内存区域 ,底层基于 StringTable(哈希表结构)实现,用于存储字符串字面量。
**核心作用:**复用相同内容的字符串,避免重复创建对象
关键特性:字符串不可变 是常量池能安全复用的前提------字符串值不可修改,不会出现多线程并发修改导致的数据混乱。
二、两种字符串创建方式 🐦🔥
Java 中创建字符串有字面量 和 new String() 两种方式,在常量池中的行为完全不同,这也是面试最常考的点!!! ⚠️
1. 字面量创建(自动入池)
java
String s1 = "Java";
String s2 = "Java";
执行流程:
- JVM 先检查常量池中是否存在
"Java" - 存在 → 直接返回常量池中的引用
- 不存在 → 在常量池创建
"Java",再返回引用
结果:s1 == s2 → true(指向同一个常量池对象)
2. new String() 创建(堆中创建,不入池)
java
String s3 = new String("Java");
执行流程:
- 先处理字面量
"Java":常量池无则创建 - 在堆内存中新建一个 String 对象
- 变量
s3指向堆对象,而非常量池对象
结果:s1 == s3 → false(引用地址不同)
经典面试题:
new String("Java")创建几个对象?
答案:1个或2个
- 常量池无
"Java"→ 常量池1个 + 堆1个(共2个) - 常量池已有
"Java"→ 仅堆1个
三、手动入池:String.intern() 🥝
intern() 是 String 提供的 native 方法,用于手动将字符串加入常量池,并返回常量池中的引用,是实现字符串复用的关键。
核心规则 👏
- 常量池已存在相同内容字符串 → 直接返回池中的引用
- 常量池不存在 → 将当前字符串加入池,返回池引用
代码示例
java
String s1 = new String("Java");
String s2 = s1.intern(); // 手动入池
String s3 = "Java";
System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true
解释:s1 指向堆对象,s2/s3 都指向常量池对象,因此引用相等。
四、JDK 版本关键差异
字符串常量池在 JDK 1.6 和 JDK 1.7+ 有重大变化,直接影响 intern() 行为与内存布局:
| 版本 | 常量池位置 | intern() 行为 |
|---|---|---|
| JDK 1.6 及之前 | 方法区(永久代) | 复制堆字符串到常量池,返回新对象引用 |
| JDK 1.7+ | 堆内存 | 仅记录堆对象引用到常量池,不复制对象 |
重要影响:
- JDK 1.7+ 常量池移至堆,避免永久代 OOM
intern()不再创建新对象,内存效率更高
JDK 1.7 为什么要将字符串常量池移动到堆中?
主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集(Full GC)的时候才会被执行GC。而Java程序中通常有大量被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效地及时回收字符串内存
五、易错场景 ⚠️
坑1:字符串拼接的常量池行为
- 编译期常量拼接:
"Ja" + "va"→ 直接优化为"Java",入常量池 - 变量拼接:
String s = a + b→ 底层用StringBuilder实现,结果在堆中,不入池
java
String a = "Ja";
String b = "va";
String s1 = a + b; // 堆对象
String s2 = "Java";
System.out.println(s1 == s2); // false
坑2:== 与 equals() 混用
==比较引用地址,常量池复用才会为 trueequals()比较字符串内容,业务判断必用 equals()
六、终极栗子 👏🌰👏
java
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; // "ab"
String s4 = s1 + s2; // new String("ab")
String s5 = "ab";
String s6 = s4.intern(); // "ab"已经存在,所以s4不会入池,仍然在堆中,但是会返回"ab"给s6
// 问
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // true
java
String x2 = new String("c") + new String("d"); // new String("cd") 并且常量池中没有"cd"
String x1 = "cd";
x2.intern(); // 常量池已有"cd",X2不入池
System.out.println(x1 == x2); // false
java
String x2 = new String("c") + new String("d"); // new String("cd") 并且常量池中没有"cd"
x2.intern(); // 常量池中没有"cd",将x2入池
String x1 = "cd";
System.out.println(x1 == x2); // true
String x2 = new String("c") + new String("d") 对应的底层原理:
String x2 = new StringBuilder()
.append©
.append(d)
.toString();
因此常量池中没有"cd"
java
String c = new String("c");
String d = new String("d");
String x2 = c + d ;
x2.intern();
String x1 = "cd";
System.out.println(x1 == x2);
String x2 = c + d 对应的底层原理:
String x2 = new StringBuilder()
.append©
.append(d)
.toString();
因此常量池中没有"cd"
如果我的内容对你有帮助,请 点赞 , 评论 , 收藏 。创作不易,大家的支持就是我坚持下去的动力!
