java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JVM字符串常量池

JVM字符串常量池用法及说明

作者:青衫码上行

还在为字符串常量池的面试题发愁吗?本文用通俗易懂的方式讲清常量池的底层原理、字面量与new String()的区别、intern()方法的使用,以及JDK1.6和1.7+版本的关键变化,帮你彻底搞懂字符串比较和内存复用,轻松应对技术面试

一、什么是字符串常量池?

字符串常量池是 JVM 专门维护的一块共享内存区域,底层基于 StringTable(哈希表结构)实现,用于存储字符串字面量。

核心作用:复用相同内容的字符串,避免重复创建对象

关键特性:字符串不可变 是常量池能安全复用的前提——字符串值不可修改,不会出现多线程并发修改导致的数据混乱。

二、两种字符串创建方式

Java 中创建字符串有字面量和 new String() 两种方式,在常量池中的行为完全不同,这也是面试最常考的点!!! 

1. 字面量创建(自动入池)

String s1 = "Java";
String s2 = "Java";

执行流程:

  1. JVM 先检查常量池中是否存在 "Java"
  2. 存在 → 直接返回常量池中的引用
  3. 不存在 → 在常量池创建 "Java",再返回引用

结果:s1 == s2 → true(指向同一个常量池对象)

2. new String() 创建(堆中创建,不入池)

String s3 = new String("Java");

执行流程:

  1. 先处理字面量 "Java":常量池无则创建
  2. 在堆内存中新建一个 String 对象
  3. 变量 s3 指向堆对象,而非常量池对象

结果:s1 == s3 → false(引用地址不同)

经典面试题:new String("Java") 创建几个对象?

答案:1个或2个

三、手动入池:String.intern() 

intern() 是 String 提供的 native 方法,用于手动将字符串加入常量池,并返回常量池中的引用,是实现字符串复用的关键。

核心规则 

  1. 常量池已存在相同内容字符串 → 直接返回池中的引用
  2. 常量池不存在 → 将当前字符串加入池,返回池引用

代码示例

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 为什么要将字符串常量池移动到堆中?

主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集(Full GC)的时候才会被执行GC。而Java程序中通常有大量被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效地及时回收字符串内存

五、易错场景 

坑1:字符串拼接的常量池行为

String a = "Ja";
String b = "va";
String s1 = a + b; // 堆对象
String s2 = "Java";
System.out.println(s1 == s2); // false

坑2:== 与 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"

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文