《Java虛擬機原理圖解》 1.2.二、Class文件中的常量池詳解(上)

主題:   http://blog.csdn.net/column/details/jvm-principle.html

《Java虛擬機原理圖解》5. JVM類加載器機制與類加載過程

《Java虛擬機原理圖解》4.JVM機器指令集

《Java虛擬機原理圖解》三、JVM運行時數據區

 

NO1.常量池在class文件的什麼位置?

          個人上一篇文章《Java虛擬機原理圖解》 一、class文件基本組織結構中已經提到了class的文件結構,在class文件中的魔數、副版本號、主版本以後,緊接着就是常量池的數據區域了,以下圖用紅線包括的位置:html

  

     知道了常量池的位置後,而後讓咱們來揭祕常量池裏究竟有什麼東西吧~     前端

NO2.常量池的裏面是怎麼組織的?

      常量池的組織很簡單,前端的兩個字節佔有的位置叫作常量池計數器(constant_pool_count),它記錄着常量池的組成元素  常量池項(cp_info) 的個數。緊接着會排列着constant_pool_count-1常量池項(cp_info)。以下圖所示:java

   

 

NO3.常量池項 (cp_info) 的結構是什麼?

   每一個常量池項(cp_info) 都會對應記錄着class文件中的某中類型的字面量。讓咱們先來了解一下常量池項(cp_info)的結構吧:數組

    

     JVM虛擬機規定了不一樣的tag值和不一樣類型的字面量對應關係以下:jvm

    

           因此根據cp_info中的tag 不一樣的值,能夠將cp_info 更細化爲如下結構體:工具

                   CONSTANT_Utf8_info,CONSTANT_Integer_info,CONSTANT_Float_info,CONSTANT_Long_info,
            CONSTANT_Double_info,CONSTANT_Class_info,CONSTANT_String_info,CONSTANT_Fieldref_info,
            CONSTANT_Methodref_info,CONSTANT_InterfaceMethodref_info,CONSTANT_NameAndType_info,CONSTANT_MethodHandle_info,
            CONSTANT_MethodType_info,CONSTANT_InvokeDynamic_info
ui

           

             如今讓咱們看一下細化了的常量池的結構會是相似下圖所示的樣子:編碼

                
          

NO4.常量池可以表示那些信息?

 

 

NO5. int和float數據類型的常量在常量池中是怎樣表示和存儲的?(CONSTANT_Integer_info, CONSTANT_Float_info)

 Java語言規範規定了 int類型和Float 類型的數據類型佔用 4 個字節的空間。那麼存在於class字節碼文件中的該類型的常量是如何存儲的呢?相應地,在常量池中,將 int和Float類型的常量分別使用CONSTANT_Integer_info和 Constant_float_info表示,他們的結構以下所示:lua

舉例:建下面的類 IntAndFloatTest.java,在這個類中,咱們聲明瞭五個變量,可是取值就兩種int類型的10 和Float類型的11fspa

 

[java]  view plain  copy
  1. package com.louis.jvm;  
  2.   
  3. public class IntAndFloatTest {  
  4.       
  5.     private final int a = 10;  
  6.     private final int b = 10;  
  7.     private float c = 11f;  
  8.     private float d = 11f;  
  9.     private float e = 11f;  
  10.       
  11. }  


 

而後用編譯器編譯成IntAndFloatTest.class字節碼文件,咱們經過javap -v IntAndFloatTest 指令來看一下其常量池中的信息,能夠看到雖然咱們在代碼中寫了兩次10 和三次11f,可是常量池中,就只有一個常量10 和一個常量11f,以下圖所示:

從結果上能夠看到常量池第#8 個常量池項(cp_info) 就是CONSTANT_Integer_info,值爲10;第#23常量池項(cp_info) 就是CONSTANT_Float_info,值爲11f。(常量池中其餘的東西先別糾結啦,咱們會面會一一講解的哦)。

 代碼中全部用到 int 類型 10 的地方,會使用指向常量池的指針值#8 定位到第#8 個常量池項(cp_info),即值爲10的結構體 CONSTANT_Integer_info,而用到float類型的11f時,也會指向常量池的指針值#23來定位到第#23個常量池項(cp_info) 即值爲11f的結構體CONSTANT_Float_info。以下圖所示:

 

NO6. long和 double數據類型的常量在常量池中是怎樣表示和存儲的?(CONSTANT_Long_info、CONSTANT_Double_info )

Java語言規範規定了 long 類型和 double類型的數據類型佔用8 個字節的空間。那麼存在於class 字節碼文件中的該類型的常量是如何存儲的呢?相應地,在常量池中,將longdouble類型的常量分別使用CONSTANT_Long_info和Constant_Double_info表示,他們的結構以下所示:

     舉例:建下面的類 LongAndDoubleTest.java,在這個類中,咱們聲明瞭六個變量,可是取值就兩種Long 類型的-6076574518398440533L 和Double 類型的10.1234567890D

[java]  view plain  copy
  1. package com.louis.jvm;  
  2.   
  3. public class LongAndDoubleTest {  
  4.       
  5.     private long a = -6076574518398440533L;  
  6.     private long b = -6076574518398440533L;  
  7.     private long c = -6076574518398440533L;  
  8.     private double d = 10.1234567890D;  
  9.     private double e = 10.1234567890D;  
  10.     private double f = 10.1234567890D;  
  11. }  


 

     而後用編譯器編譯成 LongAndDoubleTest.class 字節碼文件,咱們經過javap -v LongAndDoubleTest指令來看一下其常量池中的信息,能夠看到雖然咱們在代碼中寫了三次-6076574518398440533L 和三次10.1234567890D,可是常量池中,就只有一個常量-6076574518398440533L 和一個常量10.1234567890D,以下圖所示:

      從結果上能夠看到常量池第 #18 個常量池項(cp_info) 就是CONSTANT_Long_info,值爲-6076574518398440533L ;第 #26常量池項(cp_info) 就是CONSTANT_Double_info,值爲10.1234567890D。(常量池中其餘的東西先別糾結啦,咱們會面會一一講解的哦)。

       代碼中全部用到 long 類型-6076574518398440533L 的地方,會使用指向常量池的指針值#18 定位到第#18 個常量池項(cp_info),即值爲-6076574518398440533L 的結構體CONSTANT_Long_info,而用到double類型的10.1234567890D時,也會指向常量池的指針值#26 來定位到第 #26 個常量池項(cp_info) 即值爲10.1234567890D的結構體CONSTANT_Double_info。以下圖所示:

 

NO7. String類型的字符串常量在常量池中是怎樣表示和存儲的?(CONSTANT_String_info、CONSTANT_Utf8_info)

      對於字符串而言,JVM會將字符串類型的字面量以UTF-8 編碼格式存儲到在class字節碼文件中。這麼說可能有點摸不着北,咱們先從直觀的Java源碼中中出現的用雙引號"" 括起來的字符串來看,在編譯器編譯的時候,都會將這些字符串轉換成CONSTANT_String_info結構體,而後放置於常量池中。其結構以下所示:

     如上圖所示的結構體,CONSTANT_String_info結構體中的string_index的值指向了CONSTANT_Utf8_info結構體,而字符串的utf-8編碼數據就在這個結構體之中。以下圖所示:

請看一例,定義一個簡單的StringTest.java類,而後在這個類里加一個"JVM原理" 字符串,而後,咱們來看看它在class文件中是怎樣組織的。

[java]  view plain  copy
  1. package com.louis.jvm;  
  2.   
  3. public class StringTest {  
  4.     private String s1 = "JVM原理";  
  5.     private String s2 = "JVM原理";  
  6.     private String s3 = "JVM原理";  
  7.     private String s4 = "JVM原理";  
  8. }  

將Java源碼編譯成StringTest.class文件後,在此文件的目錄下執行 javap -v StringTest 命令,會看到以下的常量池信息的輪廓:

 

 (PS :使用javap -v 指令能看到易於咱們閱讀的信息,查看真正的字節碼文件能夠使用HEXWin、NOTEPAD++、UtraEdit 等工具。)

       在面的圖中,咱們能夠看到CONSTANT_String_info結構體位於常量池的第#15個索引位置。而存放"Java虛擬機原理" 字符串的 UTF-8編碼格式的字節數組被放到CONSTANT_Utf8_info結構體中,該結構體位於常量池的第#16個索引位置。上面的圖只是看了個輪廓,讓咱們再深刻地看一下它們的組織吧。請看下圖:

由上圖可見:「JVM原理」的UTF-8編碼的數組是:4A564D E5 8E 9FE7 90 86,而且存入了CONSTANT_Utf8_info結構體中。

  

NO8. 類文件中定義的類名和類中使用到的類在常量池中是怎樣被組織和存儲的?(CONSTANT_Class_info)

     JVM會將某個Java 類中全部使用到了的類的徹底限定名 以二進制形式的徹底限定名 封裝成CONSTANT_Class_info結構體中,而後將其放置到常量池裏。CONSTANT_Class_info 的tag值爲 7 。其結構以下:

        

Tips: 類的徹底限定名二進制形式的徹底限定名

      在某個Java源碼中,咱們會使用不少個類,好比咱們定義了一個 ClassTest的類,並把它放到com.louis.jvm 包下,則 ClassTest類的徹底限定名爲com.louis.jvm.ClassTest,將JVM編譯器將類編譯成class文件後,此徹底限定名在class文件中,是以二進制形式的徹底限定名存儲的,即它會把徹底限定符的"."換成"/" ,即在class文件中存儲的 ClassTest類的徹底限定名稱是"com/louis/jvm/ClassTest"。由於這種形式的徹底限定名是放在了class二進制形式的字節碼文件中,因此就稱之爲 二進制形式的徹底限定名。

舉例,咱們定義一個很簡單的ClassTest類,來看一下常量池是怎麼對類的徹底限定名進行存儲的。

[java]  view plain  copy
  1. package com.jvm;  
  2. import  java.util.Date;  
  3. public class ClassTest {  
  4.     private Date date =new Date();  
  5. }  

將Java源碼編譯成ClassTest.class文件後,在此文件的目錄下執行 javap -v ClassTest 命令,會看到以下的常量池信息的輪廓:

如上圖所示,在ClassTest.class文件的常量池中,共有 3 個CONSTANT_Class_info結構體,分別表示ClassTest 中用到的Class信息。 咱們就看其中一個表示com/jvm.ClassTestCONSTANT_Class_info 結構體。它在常量池中的位置是#1,它的name_index值爲#2,它指向了常量池的第2 個常量池項,以下所示:

注意:

     對於某個類而言,其class文件中至少要有兩個CONSTANT_Class_info常量池項,用來表示本身的類信息和其父類信息。(除了java.lang.Object類除外,其餘的任何類都會默認繼承自java.lang.Object)若是類聲明實現了某些接口,那麼接口的信息也會生成對應的CONSTANT_Class_info常量池項。

  除此以外,若是在類中使用到了其餘的類,只有真正使用到了相應的類,JDK編譯器纔會將類的信息組成CONSTANT_Class_info常量池項放置到常量池中。以下圖:

[java]  view plain  copy
  1. package com.louis.jvm;  
  2.   
  3. import java.util.Date;  
  4.   
  5. public  class Other{  
  6.     private Date date;  
  7.       
  8.     public Other()  
  9.     {  
  10.         Date da;  
  11.     }  
  12. }  
  上述的Other的類,在JDK將其編譯成class文件時,常量池中並無java.util.Date對應的CONSTANT_Class_info常量池項,爲何呢?

   在Other類中雖然定義了Date類型的兩個變量date、da,可是JDK編譯的時候,認爲你只是聲明瞭「Ljava/util/Date」類型的變量,並無實際使用到Ljava/util/Date類。將類信息放置到常量池中的目的,是爲了在後續的代碼中有可能會反覆用到它。很顯然,JDK在編譯Other類的時候,會解析到Date類有沒有用到,發現該類在代碼中就沒有用到過,因此就認爲沒有必要將它的信息放置到常量池中了。

   將上述的Other類改寫一下,僅使用new Date(),以下圖所示:

 

[java]  view plain  copy
  1. package com.louis.jvm;  
  2.   
  3. import java.util.Date;  
  4.   
  5. public  class Other{  
  6.     public Other()  
  7.     {  
  8.         new Date();  
  9.     }  
  10. }  

  這時候使用javap -v Other ,能夠查看到常量池中有表示java/util/Date的常量池項:

 

 

  總結:

     1.對於某個類或接口而言,其自身、父類和繼承或實現的接口的信息會被直接組裝成CONSTANT_Class_info常量池項放置到常量池中;  

     2. 類中或接口中使用到了其餘的類,只有在類中實際使用到了該類時,該類的信息纔會在常量池中有對應的CONSTANT_Class_info常量池項;

     3. 類中或接口中僅僅定義某種類型的變量,JDK只會將變量的類型描述信息以UTF-8字符串組成CONSTANT_Utf8_info常量池項放置到常量池中,上面在類中的private Date date;JDK編譯器只會將表示date的數據類型的「Ljava/util/Date」字符串放置到常量池中。

 
相關文章
相關標籤/搜索