JVM字符串常量池用法及说明
作者:青衫码上行
还在为字符串常量池的面试题发愁吗?本文用通俗易懂的方式讲清常量池的底层原理、字面量与new String()的区别、intern()方法的使用,以及JDK1.6和1.7+版本的关键变化,帮你彻底搞懂字符串比较和内存复用,轻松应对技术面试
一、什么是字符串常量池?
字符串常量池是 JVM 专门维护的一块共享内存区域,底层基于 StringTable(哈希表结构)实现,用于存储字符串字面量。
核心作用:复用相同内容的字符串,避免重复创建对象
关键特性:字符串不可变 是常量池能安全复用的前提——字符串值不可修改,不会出现多线程并发修改导致的数据混乱。
二、两种字符串创建方式
Java 中创建字符串有字面量和 new String() 两种方式,在常量池中的行为完全不同,这也是面试最常考的点!!!
1. 字面量创建(自动入池)
String s1 = "Java"; String s2 = "Java";
执行流程:
- JVM 先检查常量池中是否存在
"Java" - 存在 → 直接返回常量池中的引用
- 不存在 → 在常量池创建
"Java",再返回引用
结果:s1 == s2 → true(指向同一个常量池对象)
2. new String() 创建(堆中创建,不入池)
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 方法,用于手动将字符串加入常量池,并返回常量池中的引用,是实现字符串复用的关键。
核心规则
- 常量池已存在相同内容字符串 → 直接返回池中的引用
- 常量池不存在 → 将当前字符串加入池,返回池引用
代码示例
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实现,结果在堆中,不入池
String a = "Ja"; String b = "va"; String s1 = a + b; // 堆对象 String s2 = "Java"; System.out.println(s1 == s2); // false
坑2:== 与 equals() 混用
==比较引用地址,常量池复用才会为 trueequals()比较字符串内容,业务判断必用 equals()
六、终极栗子
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
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
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"
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"
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
