java虛擬機:運行時常量池


一、運行時常量池簡介

運行時常量池(Runtime Constant Pool),它是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后存放到常量池中

運行時常量是相對於常量來說的,它具備一個重要特征是:動態性。當然,值相同的動態常量與我們通常說的常量只是來源不同,但是都是儲存在池內同一塊內存區域。Java語言並不要求常量一定只能在編譯期產生,運行期間也可能產生新的常量,這些常量被放在運行時常量池中。這里所說的常量包括:基本類型包裝類(包裝類不管理浮點型,整形只會管理-128到127)和String(也可以通過String.intern()方法可以強制將String放入常量池)

二、 Class文件中的信息常量池

在Class文件結構中,最頭的4個字節用於存儲Megic Number,用於確定一個文件是否能被JVM接受,再接着4個字節用於存儲版本號,前2個字節存儲次版本號,后2個存儲主版本號,再接着是用於存放常量的常量池,由於常量的數量是不固定的,所以常量池的入口放置一個U2類型的數據(constant_pool_count)存儲常量池容量計數值。

常量池主要用於存放兩大類常量:字面量(Literal)和符號引用量(Symbolic References),字面量相當於Java語言層面常量的概念,如文本字符串,聲明為final的常量值等,符號引用則屬於編譯原理方面的概念,包括了如下三種類型的常量:

  • 類和接口的全限定名
  • 字段名稱和描述符
  • 方法名稱和描述符

三、 常量池的好處

常量池是為了避免頻繁的創建和銷毀對象而影響系統性能,其實現了對象的共享。例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。

  • 節省內存空間:常量池中所有相同的字符串常量被合並,只占用一個空間。
  • 節省運行時間:比較字符串時,==比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就可以判斷實際值是否相等。

雙等號==的含義

  • 基本數據類型之間應用雙等號,比較的是他們的數值。
  • 復合數據類型(類)之間應用雙等號,比較的是他們在內存中的存放地址。

四、 基本類型的包裝類和常量池

java中基本類型的包裝類的大部分都實現了常量池技術,即Byte,Short,Integer,Long,Character,Boolean。這5種包裝類默認創建了數值[-128,127]的相應類型的緩存數據,但是超出此范圍仍然會去創建新的對象。 兩種浮點數類型的包裝類Float,Double並沒有實現常量池技術

1)Integer與常量池

復制代碼
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);

System.out.println("i1=i2   " + (i1 == i2));
System.out.println("i1=i2+i3   " + (i1 == i2 + i3));
System.out.println("i1=i4   " + (i1 == i4));
System.out.println("i4=i5   " + (i4 == i5));
System.out.println("i4=i5+i6   " + (i4 == i5 + i6));  
System.out.println("40=i5+i6   " + (40 == i5 + i6));


i1=i2   true
i1=i2+i3   true
i1=i4   false
i4=i5   false
i4=i5+i6   true
40=i5+i6   true
復制代碼
解釋:
  • Integer i1=40;Java在編譯的時候會直接將代碼封裝成Integer i1=Integer.valueOf(40);,從而使用常量池中的對象。
  • Integer i1 = new Integer(40);這種情況下會創建新的對象。
  • 語句i4 == i5 + i6,因為+這個操作符不適用於Integer對象,首先i5和i6進行自動拆箱操作,進行數值相加,即i4 == 40。然后Integer對象無法與數值進行直接比較,所以i4自動拆箱轉為int值40,最終這條語句轉為40 == 40進行數值比較。

2)String與常量池-普通方法賦值

復制代碼
String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//false

String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
System.out.println("string" == "str" + "ing");// true System.out.println(str3 == str4);//false String str5 = "string"; System.out.println(str3 == str5);//true
復制代碼
解釋:
  • "abcd"是在常量池中拿對象,new String("abcd")是直接在堆內存空間創建一個新的對象。只要使用new方法,便需要創建新的對象
  • 連接表達式 +,只有使用引號包含文本的方式創建的String對象之間使用“+”連接產生的新對象才會被加入常量池中
  • 對於字符串變量的“+”連接表達式,它所產生的新對象都不會被加入字符串池中,其屬於在運行時創建的字符串,具有獨立的內存地址,所以不引用自同一String對象。

3)String與常量池-靜態方法賦值

復制代碼
public static final String A; // 常量A
public static final String B;    // 常量B
static {  
   A = "ab";  
   B = "cd";  
}  
public static void main(String[] args) {  
// 將兩個常量用+連接對s進行初始化  
String s = A + B;  
String t = "abcd";  
if (s == t) {  
    System.out.println("s等於t,它們是同一個對象");  
  } else {  
    System.out.println("s不等於t,它們不是同一個對象");  
  }  
}
復制代碼
解釋:

s不等於t,它們不是同一個對象。A和B雖然被定義為常量,但是它們都沒有馬上被賦值。在運算出s的值之前,他們何時被賦值,以及被賦予什么樣的值,都是個變數。因此A和B在被賦值之前,性質類似於一個變量。那么s就不能在編譯期被確定,而只能在運行時被創建了。

4)String與常量池-intern方法

復制代碼
public static void main(String[] args) {
  String s1 = new String("計算機");
  String s2 = s1.intern();
  String s3 = "計算機";
  System.out.println("s1 == s2? " + (s1 == s2));
  System.out.println("s3 == s2? " + (s3 == s2));
}
s1 == s2? false s3 == s2? true
復制代碼
解釋:

String的intern()方法會查找在常量池中是否存在一份equal相等的字符串,如果有則返回該字符串的引用,如果沒有則添加自己的字符串進入常量池

5)String與常量池-延伸

String s1 = new String("xyz"); //創建了幾個對象?
解釋:

考慮類加載階段和實際執行時。

  • 類加載對一個類只會進行一次。”xyz”在類加載時就已經創建並駐留了(如果該類被加載之前已經有”xyz”字符串被駐留過則不需要重復創建用於駐留的”xyz”實例)。駐留的字符串是放在全局共享的字符串常量池中的。
  • 在這段代碼后續被運行的時候,”xyz”字面量對應的String實例已經固定了,不會再被重復創建。所以這段代碼將常量池中的對象復制一份放到heap中,並且把heap中的這個對象的引用交給s1 持有

這條語句創建了2個對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM