为什么这段代码在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 == s2false,堆对象 ≠ 永久代对象。

  • s2 == s3true,都是永久代同一个对象。

🔹 JDK 7/8 的行为:直接引用堆中的对象

JDK 7 开始,字符串常量池被移到了堆里,目的是解决永久代内存不足的 OOM 问题。同时 intern() 的策略也变了:如果堆里已经存在和字符串内容相同的对象,intern() 就直接返回那个堆对象的引用,不会再复制一份。

  • new String("计算机") 仍然会在常量池创建 "计算机"(如果还没有),并在堆里创建一个新对象。此时常量池里的引用实际上直接指向这个堆对象。

  • s1.intern() 检查常量池,发现已经有 "计算机" 且它指向的是堆里的 s1,就直接返回 s1 的引用。所以 s2s1 指向同一个堆对象。

  • s3 = "计算机" 同样指向常量池记录的同一个堆对象。

结果:

  • s1 == s2true,都是同一个堆对象。

  • s2 == s3true,自然也为 true。

在 JDK 7 之前是 false/true,在 JDK 7+ 变成 true/true,这就是版本差异的直接原因。

🔹 如果给的不是这个案例,该怎么分析?

这类“不同 JDK 版本结果不同”的题,排查思路一般是:

  1. 查 JEP 或 Release Notes:看字符串常量池、垃圾收集器、集合类、类型推断等重大变更。

  2. 看底层存储结构:Stringchar[]byte[](JDK 9)会影响内存占用但不影响 == 语义;HashMap 在 JDK 8 引入红黑树,会改变遍历顺序(如果代码依赖遍历顺序,结果会变);接口默认方法(JDK 8)、模块化(JDK 9)对反射的影响等。

  3. 关注运行时区域:永久代变元空间(JDK 8)、常量池移入堆(JDK 7)——这是 intern() 行为改变的根本。

  4. 数值计算与浮点:strictfp 关键字的浮点严格性在 JDK 17 后重新引入变化,也可能导致科学计算类代码结果微差。

分析时我会在代码上加对应 JDK 的参数跑一遍,或者直接读 JDK 源码中对应类的实现注释,确认差异点。”