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虛擬機器》一書,部分程式碼參考了網上多個部落格的內容,僅用於學習。無意侵犯版權。