【从零开始学习JVM】字符串常量池


🌈 个人主页: 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 为什么要将字符串常量池移动到堆中?)
    • [五、易错场景 ⚠️](#五、易错场景 ⚠️)
    • [六、终极栗子 👏🌰👏](#六、终极栗子 👏🌰👏)

Java 字符串常量池

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

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

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

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


二、两种字符串创建方式 🐦‍🔥

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

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

java 复制代码
String s1 = "Java";
String s2 = "Java";

执行流程:

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

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

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

java 复制代码
String s3 = new String("Java");

执行流程:

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

结果:s1 == s3false(引用地址不同)

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

答案:1个或2个

  • 常量池无 "Java" → 常量池1个 + 堆1个(共2个)
  • 常量池已有 "Java" → 仅堆1个

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

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

核心规则 👏

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

代码示例

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() 混用

  • == 比较引用地址,常量池复用才会为 true
  • equals() 比较字符串内容,业务判断必用 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"


如果我的内容对你有帮助,请 点赞 , 评论 , 收藏 。创作不易,大家的支持就是我坚持下去的动力!

相关推荐
星幻元宇VR2 小时前
VR动感科普单车|让交通安全教育更有参与感
科技·学习·安全·vr·虚拟现实
2401_837163892 小时前
CSS如何实现列表项序号自定义_利用--before与content实现
jvm·数据库·python
u0109147602 小时前
Go语言怎么做WASM_Go语言WebAssembly教程【对比】
jvm·数据库·python
历程里程碑2 小时前
55 Linux epoll高效IO实战指南
java·linux·服务器·开发语言·前端·javascript·c++
何包蛋H2 小时前
Java并发编程核心:JUC、AQS、CAS 完全指南
java·开发语言
pele2 小时前
HTML5中WebSocket构造函数及其初始化连接规范
jvm·数据库·python
鱼鳞_2 小时前
Java学习笔记_Day35(多线程)
java·笔记·学习
木易 士心2 小时前
MyBatis Plus 核心功能与用法
java·后端·mybatis
m0_515098422 小时前
如何创建哈希分区表_PARTITION BY HASH解决数据分布不均与热点块
jvm·数据库·python