String類中intern方法的原理分析

  • 2019 年 10 月 3 日
  • 筆記

一,前言

​ 昨天簡單整理了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&trade; 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記憶體的相關知識,本篇部落格是接著上一篇內容的缺失的知識點。

​ 以上內容均是自主學習總結的,如有不適之處還請留言(郵箱)指教。

感謝閱讀!