程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 觸摸java常量池,觸摸java常量

觸摸java常量池,觸摸java常量

編輯:JAVA綜合教程

觸摸java常量池,觸摸java常量


     java常量池是一個經久不衰的話題,也是面試官的最愛,題目花樣百出,小菜早就對常量池有所耳聞,這次好好總結一下。

 

理論

     

     小菜先拙劣的表達一下jvm虛擬內存分布:

復制代碼

 1 String s1 = "Hello";
 2 String s2 = "Hello";
 3 String s3 = "Hel" + "lo";
 4 String s4 = "Hel" + new String("lo");
 5 String s5 = new String("Hello");
 6 String s6 = s5.intern();
 7 String s7 = "H";
 8 String s8 = "ello";
 9 String s9 = s7 + s8;
10           
11 System.out.println(s1 == s2);  // true
12 System.out.println(s1 == s3);  // true
13 System.out.println(s1 == s4);  // false
14 System.out.println(s1 == s9);  // false
15 System.out.println(s4 == s5);  // false
16 System.out.println(s1 == s6);  // true
復制代碼

     首先說明一點,在java 中,直接使用==操作符,比較的是兩個字符串的引用地址,並不是比較內容,比較內容請用String.equals()。

     s1 == s2這個非常好理解,s1、s2在賦值時,均使用的字符串字面量,說白話點,就是直接把字符串寫死,在編譯期間,這種字面量會直接放入class文件的常量池中,從而實現復用,載入運行時常量池後,s1、s2指向的是同一個內存地址,所以相等。

     s1 == s3這個地方有個坑,s3雖然是動態拼接出來的字符串,但是所有參與拼接的部分都是已知的字面量,在編譯期間,這種拼接會被優化,編譯器直接幫你拼好,因此String s3 = "Hel" + "lo";在class文件中被優化成String s3 = "Hello";,所以s1 == s3成立。

     s1 == s4當然不相等,s4雖然也是拼接出來的,但new String("lo")這部分不是已知字面量,是一個不可預料的部分,編譯器不會優化,必須等到運行時才可以確定結果,結合字符串不變定理,鬼知道s4被分配到哪去了,所以地址肯定不同。配上一張簡圖理清思路:

           必須要關注編譯期的行為,才能更好的理解常量池。

           運行時常量池中的常量,基本來源於各個class文件中的常量池。

           程序運行時,除非手動向常量池中添加常量(比如調用intern方法),否則jvm不會自動添加常量到常量池。

 

     以上所講僅涉及字符串常量池,實際上還有整型常量池、浮點型常量池等等,但都大同小異,只不過數值類型的常量池不可以手動添加常量,程序啟動時常量池中的常量就已經確定了,比如整型常量池中的常量范圍:-128~127,只有這個范圍的數字可以用到常量池。

 

實踐

     

     說了這麼多理論,接下來讓我們觸摸一下真正的常量池。

     前文提到過,class文件中存在一個靜態常量池,這個常量池是由編譯器生成的,用來存儲java源文件中的字面量(本文僅僅關注字面量),假設我們有如下java代碼:

 

1 String s = "hi";

 

     為了方便起見,就這麼簡單,沒錯!將代碼編譯成class文件後,用winhex打開二進制格式的class文件。如圖:

 復制代碼

1 //保持引用,防止自動垃圾回收
2 List<String> list = new ArrayList<String>();
3         
4 int i = 0;
5         
6 while(true){
7     //通過intern方法向常量池中手動添加常量
8     list.add(String.valueOf(i++).intern());
9 }
復制代碼

     程序立刻會拋出:Exception in thread "main" java.lang.outOfMemoryError: PermGen space異常。PermGen space正是方法區,足以說明常量池在方法區中。

     在jdk8中,移除了方法區,轉而用Metaspace區域替代,所以我們需要使用新的jvm參數:-XX:MaxMetaspaceSize=2M,依然運行如上代碼,拋出:java.lang.OutOfMemoryError: Metaspace異常。同理說明運行時常量池是劃分在Metaspace區域中。具體關於Metaspace區域的知識,請讀者自行搜索。

     本文所有代碼均在jdk7、jdk8下測試通過,其他版本jdk可能會略有差異,請讀者自行探索。

     參考文獻:《深入理解java虛擬機———jvm高級特性與最佳實踐》

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved