一,前言
昨天簡單整理了JVM內存分配和String類常用方法,遇到了String中的intern()方法。本來想一並總結起來,但是intern方法還涉及到JDK版本的問題,內容也相對較多,所以今天就彌補昨天缺失的知識點。
二,String.intern()
先來看下網上流行的關於intern()方法的示例代碼:
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
打印結果是:
JDK6:false,false
JDK7:false,true
首先我們先來回顧一個概念:
- jdk6及以前中常量池是存放在方法區中。
- jdk7常量池移動到堆中。
為什么要移動?常量池主要是存儲加載類的信息,編譯器生成的字面量和符號引用等。它的大小大約只有4M,如果大量使用intern()方法,便會造成常量池內除溢出的情況,同時GC回收也有一定的困難度。而且在jdk8以后,連Perm區域都沒有了,被替換成了元區域。
接着我們再來說說上面兩種情況不同的結果。
JDK6:
String s = new String("1");
1,這句代碼實際上是生成兩個實例對象,先在常量池中聲明為1的字符對象,再通過關鍵字new生成1的字符對象並被s引用。
2,接着 s.intern();先在常量池中查找是否有相應的字符串存在,如果有,直接返回引用,否則,在常量池中生成相應的字符串並返回引用。
3,而String s2 = "1";是屬於符號引用,直接在常量池中生成。
4,最后判斷s 是否與s2相等,通過上面的分析,s指向的是通過關鍵字new出來的在堆中的字符對象,而s2是符號引用的在常量池中的字符對象,所以返回的結果為false。如下圖所示:

String s3 = new String("1") + new String("1");同理這句代碼實際上也是產生2個字符對象,其原理和上面分析是一樣的,而最后判斷s3與s4的值,結果還是一個堆中的s3與常量池中的s4比較,那返回的結果必然是false。
JDK7:
String s1 = new String("1");
s1.intern();
String s2 = "1";
System.out.println(s == s2);
1,這一部分代碼與jdk6中的分析是類似的,但唯一不同的是s1.intern();返回的是在常量池中的引用,並不會在常量池中復制一份再返回引用。
String s2 = "1";與s1.intern();指向的是同一個引用。

2,最后比較s與s2的值,s是堆中,s2是常量池中,因而返回結果為false。
3,接着我們再來看下面一段代碼:
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
我們先來分析第一句代碼,同樣是生成兩個字符對象,先在常量池中生成為1的字符對象,再在堆中生成s3為11的字符對象。接着s3.intern();執行便去常量池中查詢是否存在為11的字符對象,但是發現沒有。這個時候在jdk7中便不會再復制一份為11到常量池中,而是直接返回堆中11的字符對象的引用。所以它與s3的引用地址是相同的。
String s4 = "11";這句代碼是符號引用,直接去常量池中創建,但是發現池中已經存在為11的字符對象的引用,因此直接返回該引用。最后判斷s3與s4是否相等,因為它們之間的引用的相同的,所以返回的結果為true。如下圖所示:

三,補充內容
本以為寫到這里就結束了,沒想到一位評論者告訴我在jdk8中,對於intern方法的處理與jdk7是不一樣的。在這里非常感謝,是我的疏忽。
下面請看jdk8中關於intern方法的源碼解釋:
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
大致解釋一下就是說,當調用intern()方法時,如果池已經包含string這個對象時,由equals(Object)方法去比較,則池中的字符串為返回。否則,將此String對象添加到池和對這個String對象的引用被返回。
四,總結
1,對於常量池的概念要注意jdk6與jdk7之間的變化。
2,intern()方法在不同jdk版本之間的變化,jdk7中如果常量池中不存在,不會再復制一份到常量池中,而是返回堆中的存在的引用地址。
3,jdk6常量池在方法區中,jdk7常量池在堆中,jdk8取消方法區,替換成元區域。
最后關於String類中intern方法就介紹這里,其實從實際開發工作中,我們只需要了解jdk7及以后版本的原理就可以了,畢竟現在市面上很少再用jdk6版本。但是這里總結出來也是為了更方便的參照理解,同時也涉及到一些關於JVM內存的相關知識,本篇博客是接着上一篇內容的缺失的知識點。
以上內容均是自主學習總結的,如有不適之處還請留言(郵箱)指教。
感謝閱讀!
