1. 程式人生 > >Java中的幾種常量池

Java中的幾種常量池

參考https://www.zhihu.com/question/55994121

1.執行時常量池:方法區的一部分,存放編譯器生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池。一般來說,除了儲存Class檔案中描述的符號引用外,還會把翻譯出來的直接引用也儲存到執行時常量池中。執行時常量池具備動態性,也就是並非預置入Class檔案的內容才能進入方法區的執行時常量池,執行期間也可能將新的常量放入池中。

2.字串常量池:本質是一個HashSet<String>,這是一個純執行時的結構,而且是惰性維護的。注意它只儲存String物件的引用,而不儲存String物件的內容,根據這個引用可以得到具體的String物件。

3.Class常量池:主要存放兩大類常量:字面量和符號引用。載入Class檔案時,Class檔案中String物件會進入字串常量池(這裡的進入是指 放入字串的引用,字串本身還是在堆中),別的大都會進入執行時常量池。

字面量比較接近Java語言層面常量的概念,如文字字串、宣告為final的常量值

符號引用屬於編譯原理的概念:

    類和介面的全定限名

    欄位的名稱和描述符

    方法的名稱和描述符

符號引用將在解析階段被替換為直接引用。因為Java程式碼在進行編譯時,並不像C那樣有"連線"這一步驟,而是在虛擬機器載入Class檔案的時候進行動態連線。也就是說,Class檔案不會儲存各個方法、欄位的最終記憶體佈局資訊,因此這些欄位、方法的符號引用不經過執行期間 轉換的話無法得到真正的記憶體入口地址,也就無法直接被虛擬機器使用。當虛擬機器執行時,需要從常量池獲得對應的符號引用,再在類建立時或執行時解析、翻譯到具體的記憶體地址之中。

4.String的intern方法:

JDK7中,如果字串常量池中已經有了這個字串,那麼直接返回常量池中的它的引用,如果沒有,那就將它的引用儲存一份到字串常量池,然後直接返回這個引用

5.字面量進入字串常量池的時機

就HotSpot VM的實現來說,載入類的時候,那些字串字面 量會進入當前類的執行時常量池,不會進入全域性字串常量池(即在字串常量池中沒有相應的引用,在堆中也沒有生成對應的物件)。載入類的時,沒有解析字串字面量,等到執行ldc指令的時候就會觸發這個解析的動作ldc指令的語義是:到當前類的執行時常量池區查詢該index對應的項,如果該項沒有解析就解析,並返回解析後的內容。在遇到String型別常量時,解析的過程是如果發現字串常量池中已經有了內容匹配的String型別的引用,就直接返回這個引用,如果沒有內容匹配的String例項的引用,就會在Java堆中建立一個對應內容的String物件,然後在字串常量池中記錄下這個引用

說明:自己的一點理解,上面說的時對字串的解析,其實對方法解析也是類似,有些方法也是lazy resolve,有一部分符號引用是在類載入階段或者第一次使用的時候就轉化為直接引用,被稱為靜態解析(例如靜態方法、私有方法等非虛方法),另一部分將在每一次執行期間轉換為直接引用,被稱為動態連線(例如靜態分派),這部分也是lazy resolve。

6.例題分析:

例1:

class Test{
	public static String s1 = "static";
	public static void main(String[] args) {
		String s2 = new String("he")+new String("llo");
		s2.intern();
		String s3 = "hello";
		System.out.println(s2==s3);  //true
	}
}

"static" "he" "llo" "hello"都會進入Class常量池,類載入階段由於解析階段時lazy的,所以不會建立例項,更不會駐留字串常量池。但要注意這個“static"和其他三個不一樣,它是靜態的,在載入階段的初始化階段,會為靜態遍歷執行初始值,也就是將"static"賦值給s1,所以會建立"static"字串物件, 並且會儲存一個指向它的引用到字串常量池。

執行main方法後,執行String s2 = new String("he")+new String("llo")語句,建立"he"和"llo"的物件,並會儲存引用到字串常量池中,然後內部建立一個StringBuilder物件,一路append,最後呼叫toString()方法得到一個String物件(內容時hello,注意這個toString方法會new一個String物件),並把它賦值給s2(注意這裡沒有把hello的引用放入字串常量池)。

然後執行語句:s1.intern(),此時字串常量池中沒有,它會將上面的這個hello物件的引用儲存到字串常量池,然後返回這個引用,但是這個返回的引用沒有變數區接收,所以沒用。

然後執行:String s3 = "hello"因為字串常量池中已經有了,所以直接指向堆中"hello"物件

然後執行:System.out.println(s2==s3),此時返回true。

示意圖如下:

例題2:

class JianZhiOffer{
	public static void main(String[] args) {
		String s1 = new String("he")+new String("llo");    //第一句
		String s2 = new String("h")+new String("ello");    //第二句
		String s3 = s1.intern();                           //第三句
		String s4 = s2.intern();                           //第四句
		System.out.println(s1==s3);                        //第五句
		System.out.println(s1==s4);                        //第六句
	}
}

類載入階段,什麼都沒幹。

第一句:建立"he"和"llo"物件,並放入字串常量池,然後建立"hello"物件,沒有放入字串常量池,s2指向這個"hello"物件。

第二句:建立了"h"和"ello"物件,並放入字串常量池,然後建立"hello"物件,沒有放入字串常量池,s3指向這個"hello"物件。

第三句:字串常量池中沒有"hello",所以會把s1指向String物件的引用放入字串常量池,然後將這個引用返回給了s3,所以s1==s3是true

第四句:字串常量池中有了"hello",所以將s4指向的s3指向的物件"hello",所以第六句s4==s1是true。

相關文獻參考:

知乎:

https://www.zhihu.com/question/55994121

https://www.zhihu.com/question/29884421/answer/113785601

https://www.zhihu.com/question/55328596

美團文章:

https://tech.meituan.com/in_depth_understanding_string_intern.html