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 编译器如何通过字符串池优化字符串内存分配。核心要点:

  1. 字面量字符串自动驻留,new 创建独立对象
  2. Java 7+ 将字符串池移至堆空间,支持垃圾回收
  3. 手动驻留需谨慎,避免内存泄漏
  4. Java 9+ 的紧凑字符串大幅提升内存效率

所有示例代码可在 GitHub 获取。