《深入理解Java虛擬機》第2版挖的坑終於在第3版中被R大填平了
intern的作用
該方法的作用是把首次遇到的字符串加載到常量池中。
對於任意兩個字符串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true。
測試代碼
String str1 = new StringBuilder("計算機").append("軟件").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2);
這段代碼在JDK 6中運行,會得到兩個false,而在JDK 7中運行,會得到一個true和一個false。
由於JDK 6常量池位於方法區,JDK 7以后常量池位於堆中,所以用兩個版本的jdk跑上面的代碼就會出現神奇的事情。甚至用JDK 8來跑,也會出現你想不到的結果。
產生差異的原因是:在JDK 6中,intern()方法會把首次遇到的字符串實例復制到永久代的字符串常量池中存儲,返回的也是永久代里面這個字符串實例的引用,而由StringBuilder創建的字符串實例在Java堆上,所以必然不是同一個引用,將返回false。
而JDK 1.7(以及部分其他虛擬機,例如JRocki)的intern()實現就不需要再拷貝字符串的實例到永久代了,既然字符串常量池已經移到了Java堆中,那只需要在常量池里記錄一下首次出現的實例引用即可,因此intern()返回的引用和由StringBuilder創建的那個字符串實例就是同一個。 輸出false,說明調用main方法之前,肯定在字符串常量池里面已經有了這個“java”字符串了,且不是首次出現的,是在new Stringbuilder之前就存在,不符合intern首次出現的原則。而new StringBuilder在對上新建對象,當然不等。
debug
我們在main方法的第一行打上一個斷點,debug運行程序后,可以看到Memory,然后過濾出String,如下:
然后雙擊過濾出來的java.lang.String,可以看到下圖:
在這個頁面我們可以繼續過濾:
果然,在程序還沒執行第14行之前,“java”已經出現了。
從這個結果我們可以推斷出:Java標准庫在JVM啟動過程中加載的部分,可能里面就有類里有引用“java”字符串字面量,這個字面量被初次引用的時候就會被intern,加入到字符串常量池中去。
sun.misc.Version的init()方法加載的。
不同的java版本加載的不一定是java字符串
比如這個示例我在JDK8u212-b03上跑出來,就是兩個true:
在這個版本里面,sun.misc.Version的launcher_name變成了“openjdk”:
那么根據我們之前的猜測,把程序成下面這樣的,效果就是一樣的了: