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

[last updated:2014/11/27]前端

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

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

  

     知道了常量池的位置後,而後讓咱們來揭祕常量池裏到底有什麼東西吧~     數組

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

      常量池的組織很是easy,前端的兩個字節佔有的位置叫作常量池計數器(constant_pool_count),它記錄着常量池的組成元素  常量池項(cp_info) 的個數。緊接着會排列着constant_pool_count-1常量池項(cp_info)。例如如下圖所看到的:jvm

   

 

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

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

    

     JVM虛擬機規定了不一樣的tag值和不一樣類型的字面量相應關係例如如下:ui

    

           因此依據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
lua

          

             現在讓咱們看一下細化了的常量池的結構會是相似下圖所看到的的樣子:spa

                
          

NO4.常量池能夠表示那些信息?

 

 

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

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

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

 

package com.louis.jvm;

public class IntAndFloatTest {
	
	private final int a = 10;
	private final int b = 10;
	private float c = 11f;
	private float d = 11f;
	private float e = 11f;
	
}


 

而後用編譯器編譯成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_infoConstant_Double_info表示,他們的結構例如如下所看到的:

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

package com.louis.jvm;

public class LongAndDoubleTest {
	
	private long a = -6076574518398440533L;
	private long b = -6076574518398440533L;
	private long c = -6076574518398440533L;
	private double d = 10.1234567890D;
	private double e = 10.1234567890D;
	private double f = 10.1234567890D;
}


 

     而後用編譯器編譯成 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文件裏是如何組織的。

package com.louis.jvm;

public class StringTest {
	private String s1 = "JVM原理";
	private String s2 = "JVM原理";
	private String s3 = "JVM原理";
	private String s4 = "JVM原理";
}

將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二進制形式的字節碼文件裏,因此就稱之爲 二進制形式的全然限定名。

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

package com.jvm;
import  java.util.Date;
public class ClassTest {
	private Date date =new Date();
}

將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常量池項放置到常量池中。例如如下圖:

package com.louis.jvm;

import java.util.Date;

public  class Other{
	private Date date;
	
	public Other()
	{
		Date da;
	}
}
  上述的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(),例如如下圖所看到的:

package com.louis.jvm;

import java.util.Date;

public  class Other{
	public Other()
	{
		new Date();
	}
}

  這時候使用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」字符串放置到常量池中。


 

 

 






 

做者的話

    本文是《Java虛擬機原理圖解》系列的當中一篇,假設您有興趣,請關注該系列的其它文章~

   認爲本文不錯,順手點個贊哦~~您的鼓舞,是我繼續分享知識的強大動力!

 

 

 

 

 

 

 

-----------------------------------------------------------------------------------------------------------------------------------------

本文源自  http://blog.csdn.net/luanlouis/,如需轉載,請註明出處,謝謝!

相關文章
相關標籤/搜索