题目
经常看到一道面试题,题目如下,每次都是猜答案,几乎每次都猜错。看到答案后,也无法解释为什么,直到最近学习了 JVM 相关的知识,才理解透彻。
// 运行环境为JDK版本1.8public static void main(String[] args) { String s1 = new String("1"); s1.intern(); String s2 = "1"; System.out.println(s1 == s2); // false String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); // true}
这道题在 JDK1.8 的环境下运行(注意:这道题与 JDK 的版本密切相关,不同版本会有不同的答案),结果分别为 false、true。s1 和 s2 的比较结果,很容易判断,而对于 s3 和 s4 的比较结果,则就不太好理解了,接下来将从字节码和 JVM 内存结构的角度来解释一下运行结果。
intern()
intern()方法是 String 类提供的一个方法,当调用一个字符串对象 s 的 intern()方法时,会先判断字符串常量池中是否存在 s 所表示的字面量(这个判断过程使用的是字符串的 equals()进行比较的,即比较的是字符串的内容),如果字符串常量池中存在该字面量,则 intern()方法不做任何操作,直接返回常量池中该字面量的地址;如果字符串常量池中不存在该字面量,那么就将该字面量放入到字符串常量池中(也就是在常量池中造了一个"对象"),然后返回常量池中该字面量的地址。
这里的关键点在于字符串常量池,在 JVM 虚拟机规范中,字符串常量池是属于方法区的一部分,而方法区只是 Java 虚拟机规范中的概念,具体如何去实现方法区,是由各个虚拟机厂商自己决定的。并且同一厂商实现的虚拟机,在不同版本中也存在不同的区别。例如 HotSpot 虚拟机,在 JDK1.6 中,整个方法区都是在永久代(PermGem)实现的;到了 JDK1.7 中,方法区也是在永久代(PermGem)实现的,但与 JDK1.6 不同的是,将方法区中的运行时常量池和字符串常量池放到了堆空间,而其他部分在还是在永久代中;再到 JDK1.8 时,则是用元空间(Metaspace)实现的方法区,即用元空间(Metaspace)取代了永久代(PermGem),元空间使用的是直接内存,但是方法区中的运行时常量池和字符串常量池依旧是在堆空间,这和 JDK1.7 是相同的。如下面的示意图所示。
![0f807e12471a4320fd54b51dbf47e6c8.png](https://i-blog.csdnimg.cn/blog_migrate/b4f2aaf73a0b3e59f57b2317a708f3aa.jpeg)
从 JDK1.6 到 JDK1.8,字符串常量池从永久代移到堆内存,对于 intern()方法,也产生了一定的变化。
「假设现在有字符串对象 s(这个对象 s 是处于堆中的),它的字符串的内容是"aa"(即字面量为"aa"),并且假设字符创常量池中也不存在字面量"aa"」 。那么在 JDK1.6 中,当调用 s.intern()方法时,由于字符串常量池中不存在"aa&#