1. 概述
String
对象是 Java 语言中使用最频繁的类。本文将深入探讨 Java 字符串池 —— 这是 JVM 专门用于存储字符串的特殊内存区域。
2. 字符串驻留机制
得益于 Java 中字符串的不可变性,JVM 可以通过 在池中仅保存每个字符串字面量的唯一副本 来优化内存分配。这个过程称为驻留(interning)。
当创建字符串变量并赋值时,JVM 会先在池中查找等值的字符串:
- ✅ 如果找到:直接返回其内存地址引用,无需额外分配内存
- ❌ 如果未找到:将其加入池中(执行驻留),然后返回引用
验证代码:
String constantString1 = "Baeldung";
String constantString2 = "Baeldung";
assertThat(constantString1)
.isSameAs(constantString2);
3. 通过构造函数创建的字符串
使用 new
操作符创建字符串时,Java 编译器会在 JVM 的堆空间中创建一个新对象。每个通过这种方式创建的字符串都指向独立的内存区域。
与前者的区别示例:
String constantString = "Baeldung";
String newString = new String("Baeldung");
assertThat(constantString).isNotSameAs(newString);
4. 字符串字面量 vs 字符串对象
两种创建方式的本质区别:
new()
操作符:始终在堆内存中创建新对象- 字面量语法(如
"Baeldung"
):- ✅ 若池中存在则返回现有对象引用
- ❌ 否则创建新对象并加入池中供后续复用
对比测试:
// 字面量创建 - 共享引用
String first = "Baeldung";
String second = "Baeldung";
System.out.println(first == second); // True
// new()创建 - 独立对象
String third = new String("Baeldung");
String fourth = new String("Baeldung");
System.out.println(third == fourth); // False
// 混合比较 - 引用不同
String fifth = "Baeldung";
String sixth = new String("Baeldung");
System.out.println(fifth == sixth); // False
⚠️ 最佳实践:优先使用字面量语法,代码更简洁且允许编译器优化。
5. 手动驻留
可通过调用 intern()
方法手动将字符串加入池:
String constantString = "interned Baeldung";
String newString = new String("interned Baeldung");
assertThat(constantString).isNotSameAs(newString);
String internedString = newString.intern();
assertThat(constantString)
.isSameAs(internedString);
6. 垃圾回收机制
字符串池的存储位置演变:
- Java 7 之前:
- 存储在 永久代(PermGen)
- ❌ 固定大小,运行时无法扩展
- ❌ 不可垃圾回收
- ⚠️ 驻留过多字符串易导致
OutOfMemoryError
- **Java 7+**:
- ✅ 转移到 堆空间
- ✅ 支持垃圾回收
- ✅ 未被引用的字符串会被清理,降低内存溢出风险
7. 性能与优化
优化参数对比
Java 版本 | 优化方式 | 示例参数 |
---|---|---|
Java 6 | 调整永久代大小 | -XX:MaxPermSize=1G |
Java 7+ | 查看池状态/调整桶数量 | -XX:+PrintStringTableStatistics -XX:StringTableSize=4901 |
⚠️ 关键参数说明:
- 默认桶数量变化:
- Java 7u40 前:1009
- Java 7u40 - Java 11:60013
- Java 11+:65536
- 增大桶数量:会消耗更多内存,但能减少字符串插入时间
8. Java 9 的改进
Java 9 引入紧凑字符串(Compact Strings):
- 旧机制(Java 8-):
- 内部使用
char[]
数组 - UTF-16 编码(每个字符占 2 字节)
- 内部使用
- 新机制(Java 9+):
- ✅ 根据内容自动选择
char[]
或byte[]
编码 - ✅ 仅必要时使用 UTF-16
- ✅ 显著减少堆内存占用
- ✅ 降低 GC 压力
- ✅ 根据内容自动选择
9. 总结
本文揭示了 JVM 和 Java 编译器如何通过字符串池优化字符串内存分配。核心要点:
- 字面量字符串自动驻留,
new
创建独立对象 - Java 7+ 将字符串池移至堆空间,支持垃圾回收
- 手动驻留需谨慎,避免内存泄漏
- Java 9+ 的紧凑字符串大幅提升内存效率
所有示例代码可在 GitHub 获取。