前言
本章我们将以String.intern()方法为切入点,深入探讨Java字符串的核心机制,包括字符串字面量、字符串常量池及其在不同JDK版本中的关键变化。
一、字符串字面量
字符串字面量是在Java语言规范中明确定义的概念,指直接使用双引号””创建的字符串值。
创建方式对比:
String a = "aaa"; // 创建字符串字面量
String b = new String("aaa"); // 新建String对象
核心特性:从JDK 1.7开始,当创建字符串字面量时,JVM会在堆中创建对应的String对象,并将其引用存入字符串常量池。后续遇到一样内容的字面量时,直接复用常量池中的引用,避免重复创建对象。
二、字符串常量池的演进
Java中有三种常量池概念,本文重点讨论全局字符串常量池。
版本演进关键变化:
- JDK 1.6:常量池位于永久代(方法区),存储的是字符串对象本身
- JDK 1.7:常量池移至堆内存,存储的是堆中字符串对象的引用
- JDK 1.8:永久代被元空间取代,但字符串常量池仍在堆中
重大认知修正:传统认为”new String()时会检查常量池,如果没有就创建”的说法不准确。实际上,对象始终在堆中创建,常量池只存储引用。
三、intern()方法的版本差异分析
通过以下经典代码展示不同JDK版本的行为差异:
@Test
public void test() {
String s = new String("2");
s.intern();
String s2 = "2";
System.out.println(s == s2); // 输出false
String s3 = new String("3") + new String("3");
s3.intern();
String s4 = "33";
System.out.println(s3 == s4); // JDK6:false, JDK7+:true
}
JDK 1.6内存模型分析

- String s = new String(“2”):在堆中创建String对象,在永久代常量池创建”2″对象
- s.intern():在常量池发现已存在”2″对象,直接返回其地址
- String s2 = “2”:返回常量池中”2″对象的地址
- s3 = new String(“3”) + new String(“3”):在堆中创建内容为”33″的String对象
- s3.intern():在常量池创建新的”33″对象
- 比较结果:s3指向堆对象,s4指向常量池对象,故为false
JDK 1.7+内存模型分析

- String s = new String(“2”):在堆中创建String对象和”2″对象,常量池保存”2″的引用
- s3.intern():将s3的堆对象引用存入常量池
- 关键差异:s4直接获得s3的引用地址
- 比较结果:s3和s4指向同一堆对象,故为true
四、变种代码分析:intern()调用时机的重大性
调整代码顺序后:
String s3 = new String("3") + new String("3");
String s4 = "33"; // 先在常量池创建引用
s3.intern(); // 发现已存在,不进行任何操作
System.out.println(s3 == s4); // JDK6/7均输出false
结论:intern()方法必须在字面量赋值之前调用,才能将堆对象的引用记录到常量池中。
五、字面量进入常量池的时机
通过字节码分析发现,字面量并非在类加载时就全部进入全局字符串常量池。
懒加载机制:
- 类加载时,字面量进入当前类的运行时常量池
- 执行ldc指令(如字面量赋值)时触发解析:检查全局字符串常量池是否存在匹配引用如存在,直接返回该引用如不存在,在堆中创建String对象并记录引用到常量池
六、字符串拼接的实现原理
+运算符的实现因操作数类型而异:
涉及String对象:
String str1 = "str1";
String str2 = "str2";
String str3 = str1 + str2; // 底层使用StringBuilder拼接
纯字面量/常量拼接:
String str = 1 + "str2" + "str3"; // 编译期直接优化为"1str2str3"
重大细节:字面量拼接时,常量池只保存最终结果,不保存中间量:
String str1 = new String("aa" + "bb"); // 常量池中无"aa"、"bb",只有"aabb"
String str2 = new StringBuilder("a").append("a").toString();
System.out.println(str2 == str2.intern()); // 输出true(由于常量池无"aa")
七、未解疑问
实验发现一样代码在不同执行环境结果可能不同:
// main方法中返回true,@Test方法中可能返回false
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
此现象可能与类加载路径或测试框架的初始化顺序有关,具体缘由有待进一步探究。
总结
- JDK版本影响:1.7+常量池引用机制显著改变了intern()行为
- 时机关键:intern()调用顺序对引用记录有决定性影响
- 性能考量:理解常量池机制有助于编写高效字符串处理代码
- 实践提议:在需要频繁比较字符串的场景合理使用intern(),但要注意版本兼容性
通过系统分析,我们深入理解了Java字符串处理的底层机制,为性能优化和问题排查提供了重大理论基础。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...