1. 程式人生 > >String放入執行時常量池的時機與String.intern()方法解惑

String放入執行時常量池的時機與String.intern()方法解惑

執行時常量池概述

Java執行時常量池中主要存放兩大類常量:字面量和符號引用。字面量比較接近於Java語言層面的常量概念,如文字字串、宣告為final的常量值等。
而符號引用則屬於編譯原理方面的概念,包括了下面三類常量:
- 類和介面的全限定名(包名+類名)
- 欄位的名稱和描述符
- 方法的名稱和描述符

執行時常量池位置

執行時常量池在JDK1.6及之前版本的JVM中是方法區的一部分,而在HotSpot虛擬機器中方法區放在了”永久代(Permanent Generation)”。所以執行時常量池也是在永久代的。
但是JDK1.7及之後版本的JVM已經將執行時常量池從方法區中移了出來,在Java 堆(Heap)中開闢了一塊區域存放執行時常量池

本文主要解惑String物件(即文字字串)何時放入常量池,不涉及上述三類符號引用常量和其他非String常量值。而且本文只討論主流的HotSpot虛擬機器。

String何時放入常量池

記住一句話:直接使用雙引號宣告出來的String物件會直接儲存在常量池中。

程式碼一:

String a = "計算機軟體";

分析:因為計算機軟體五個字直接使用了雙引號宣告,故JVM會在執行時常量池中首先查詢有沒有該字串,有則直接返回該字串在常量池中的引用;沒有則直接在常量池中建立該字串,然後返回引用。此時,該句程式碼已經執行完畢,不會在java Heap(堆)中建立內容相同的字串。該字串只在常量池中建立了一個String物件。

程式碼二:

String a = new String("計算機軟體");

分析:該行程式碼生成了兩個String物件(Stack(棧)中的物件引用不在討論範圍內):第一步,因為計算機軟體五個字直接使用了雙引號宣告,故JVM會在執行時常量池中首先查詢有沒有該字串,有則進入第二步;沒有則直接在常量池中建立該字串,然後進入第二步。第二步:在常量池中建立了一個String物件之後,由於使用了new,JVM會在Heap(堆)中建立一個內容相同的String物件,然後返回堆中String物件的引用。該行程式碼分別在常量池和堆中生成了兩個內容相同的String物件。

程式碼三:

String a = "計算機" + "軟體";

分析:由於JVM存在編譯期優化,對於兩個直接雙引號宣告的String的+操作,JVM在編譯期會直接優化為“計算機軟體”一個字串,故該行程式碼同程式碼一

程式碼四:

String b = "計算機";
String a = b + "軟體";

分析:由於b是一個String變數,編譯期無法確定b的值,故不會優化為一個字串。即使我們知道b的值,但JVM認為它是個變數,變數的值只能在執行期才能確定,故不會優化。執行期字串的+連線符相當於new,故該行程式碼在Heap中建立了一個內容為“計算機軟體”的String物件,並返回該物件的引用。至此,該程式碼執行完畢,因為沒有直接雙引號宣告計算機軟體這5個字的字串,故常量池中不會生成計算機軟體這5個字的字串。但是會有“計算機”和“軟體”這兩個String物件,因為他們都用雙引號聲明瞭。

程式碼五:

String final b = "計算機";
String a = b + "軟體";

分析:該程式碼與程式碼四的唯一區別是將b宣告為final型別,即為常量。故在編譯期JVM能確定b的值,所以對+可以優化為“計算機軟體”5個字的字串。該程式碼的運行同程式碼三和程式碼一

程式碼六:

String a = new String("計算機") + "軟體";

分析:因為有new,該程式碼也無法編譯期優化,故該行程式碼只是在Heap中生成了“計算機軟體”字串的String物件,在常量池中沒有內容相同的物件生成。

String.intern方法

概述

String.intern()是一個Native方法,它的作用是:如果執行時常量池中已經包含一個等於此String物件內容的字串,則返回常量池中該字串的引用;如果沒有,則在常量池中建立與此String內容相同的字串,並返回常量池中建立的字串的引用。

JDK1.7改變

當常量池中沒有該字串時,JDK7的intern()方法的實現不再是在常量池中建立與此String內容相同的字串,而改為在常量池中記錄Java Heap中首次出現的該字串的引用,並返回該引用
驗證程式碼:

String str1 = new StringBuilder("計算機").append("軟體").toString();
System.out.println((str1.intern() == str1));
//JDK1.6:false
//JDK1.7:true

String b = "計算機";
String a = b + "軟體";
System.out.println(a.intern() == a);
//JDK1.6:false
//JDK1.7:true    

測試程式碼

請執行以下的程式碼看看你分析的結果和真正的執行結果是否一樣,JDK1.6和1.7都要跑一遍,如果你都分析對了,那就是理解了。

//一次放開一個多行註釋執行
       /* 
        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);
        */
       /* 
        String s = new String("1");
        String s2 = "1";
        s.intern();
        System.out.println(s == s2);
        String s3 = new String("1") + new String("1");
        String s4 = "11";
        s3.intern();
        System.out.println(s3 == s4);
        */
  /*
 //+連線但編譯器不優化
        String s1=new String("xy") + "z";  
        String s2=s1.intern();  
        System.out.println( s1==s1.intern() );  
        System.out.println( s1+" "+s2 );  
        System.out.println( s2==s1.intern() ); 
        */
      /*// 一般情況
        String s1=new String("xyz") ;  
        String s2=s1.intern();  
        System.out.println( s1==s1.intern() );  
        System.out.println( s1+" "+s2 );  
        System.out.println( s2==s1.intern() ); 
        */

       /*//編譯器優化
        String s1 = "xy" + "z";
        String s2 = s1.intern();
        System.out.println( s1==s1.intern() );  
        System.out.println( s1+" "+s2 );  
        System.out.println( s2==s1.intern() ); 

        */

說明:本文有部分內容摘抄了周志明大神的《深入理解Java虛擬機器》一書,部分程式碼參考了網上多個部落格的內容,僅用於學習。無意侵犯版權。