为什么这段代码在JDK不同版本中结果不同
面试官:“为什么这段代码在 JDK 6 和 JDK 8 上运行结果不一样?”
String s1 = new String("计算机");
String s2 = s1.intern();
String s3 = "计算机";
System.out.println(s1 == s2);
System.out.println(s2 == s3);
候选人:
“这个差异的根源是字符串常量池在 JDK 7 时从永久代搬到了堆里,导致 intern() 的返回值引用策略彻底变了。JDK 6 是‘拷贝到永久代’,JDK 7 开始是‘能复用堆里的就直接复用’。下面我把两个版本的行为拆开对比,再给出一个通用的分析方法。
🔹 JDK 6 的行为:复制到永久代
在 JDK 6 及以前,字符串常量池放在永久代,是一块独立于堆的内存区域。
-
new String("计算机")做了两件事:先在常量池创建"计算机"(如果还没有),再在堆里创建一个新的String对象。所以s1指向堆里的新对象。 -
s1.intern()检查常量池,发现已经有"计算机"(因为new时已放入),就直接返回常量池里的那个引用。所以s2指向永久代的字符串对象。 -
s3 = "计算机"直接指向常量池里的同一个对象,也就是s2指向的那个。
结果:
-
s1 == s2→false,堆对象 ≠ 永久代对象。 -
s2 == s3→true,都是永久代同一个对象。
🔹 JDK 7/8 的行为:直接引用堆中的对象
JDK 7 开始,字符串常量池被移到了堆里,目的是解决永久代内存不足的 OOM 问题。同时 intern() 的策略也变了:如果堆里已经存在和字符串内容相同的对象,intern() 就直接返回那个堆对象的引用,不会再复制一份。
-
new String("计算机")仍然会在常量池创建"计算机"(如果还没有),并在堆里创建一个新对象。此时常量池里的引用实际上直接指向这个堆对象。 -
s1.intern()检查常量池,发现已经有"计算机"且它指向的是堆里的s1,就直接返回s1的引用。所以s2和s1指向同一个堆对象。 -
s3 = "计算机"同样指向常量池记录的同一个堆对象。
结果:
-
s1 == s2→true,都是同一个堆对象。 -
s2 == s3→true,自然也为 true。
在 JDK 7 之前是 false/true,在 JDK 7+ 变成 true/true,这就是版本差异的直接原因。
🔹 如果给的不是这个案例,该怎么分析?
这类“不同 JDK 版本结果不同”的题,排查思路一般是:
-
查 JEP 或 Release Notes:看字符串常量池、垃圾收集器、集合类、类型推断等重大变更。
-
看底层存储结构:
String从char[]变byte[](JDK 9)会影响内存占用但不影响==语义;HashMap在 JDK 8 引入红黑树,会改变遍历顺序(如果代码依赖遍历顺序,结果会变);接口默认方法(JDK 8)、模块化(JDK 9)对反射的影响等。 -
关注运行时区域:永久代变元空间(JDK 8)、常量池移入堆(JDK 7)——这是
intern()行为改变的根本。 -
数值计算与浮点:
strictfp关键字的浮点严格性在 JDK 17 后重新引入变化,也可能导致科学计算类代码结果微差。
分析时我会在代码上加对应 JDK 的参数跑一遍,或者直接读 JDK 源码中对应类的实现注释,确认差异点。”