程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> Java基礎:你是否了解KVM的常量池

Java基礎:你是否了解KVM的常量池

編輯:JAVA編程入門知識

  在class文件中,“常量池”是最復雜也最值得關注的內容。

  Java是一種動態連接的語言,常量池的作用非常重要,常量池中除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值還,還包含一些以文本形式出現的符號引用,比如:

  類和接口的全限定名;

  字段的名稱和描述符;

  方法和名稱和描述符。

  在C語言中,假如一個程序要調用其它庫中的函數,在連接時,該函數在庫中的位置(即相對於庫文件開頭的偏移量)會被寫在程序中,在運行時,直接去這個地址調用函數;

  而在Java語言中不是這樣,一切都是動態的。編譯時,假如發現對其它類方法的調用或者對其它類字段的引用的話,記錄進class文件中的,只能是一個文本形式的符號引用,在連接過程中,虛擬機根據這個文本信息去查找對應的方法或字段。

  

  所以,與Java語言中的所謂“常量”不同,class文件中的“常量”內容很非富,這些常量集中在class中的一個區域存放,一個緊接著一個,這裡就稱為“常量池”。

  常量池由多條“常量池項”組成,每一個常量池項又由兩部分組成,這裡分別稱為“常量池項頭”和“常量池項體”。

  常量池項頭表明常量池項的類型,常量池項共分為11種類型,分別為:

  

  常量池項類型
   值
   說明
   
  CONSTANT_Utf8
   1
   UTF-8編碼的Unicode字符串
   
  CONSTANT_Integer
   3
   int型常量
   
  CONSTANT_Float
   4
   Float型常量
   
  CONSTANT_Long
   5
   Long型常量
   
  CONSTANT_Double
   6
   double型常量
   
  CONSTANT_Class
   7
   對一個class的符號引用
   
  CONSTANT_String
   8
   String型常量
   
  CONSTANT_Fieldref
   9
   對一個字段的符號引用
   
  CONSTANT_Methodref
   10
   對一個類方法的符號引用
   
  CONSTANT_InterfaceMedthodref
   11
   對一個接口方法的符號引用
   
  CONSTANT_NameAndType
   12
   對名稱和類型的符號引用

  常量池項體中存放的就是對應的常量數據,比如各種數值型的常量或者字符串等等。

  以下介紹kvm中的常量池是如何組織起來的。

  

  數據結構:

  在KVM的頭文件kvm/vmcommon/h/pool.h中,有以下對常量池項類型的定義:

  #define CONSTANT_Utf8                       1
  #define CONSTANT_Integer                    3
  #define CONSTANT_Float                      4
  #define CONSTANT_Long                       5
  #define CONSTANT_Double                     6
  #define CONSTANT_Class                      7
  #define CONSTANT_String                     8


  
   #define CONSTANT_Fieldref                   9
  #define CONSTANT_Methodref                  10
  #define CONSTANT_InterfaceMethodref    11
  #define CONSTANT_NameAndType            12
   以及常量池項體結構的定義:

  

  union constantPoolEntryStrUCt {
      struct {
          unsigned short classIndex;
          unsigned short nameTypeIndex;
      }               method;  /* Also used by Fields */
      CLASS           clazz;
      INTERNED_STRING_INSTANCE String;
      cell           *cache;   /* Either clazz or String */
      cell            integer;
      long            length;
      NameTypeKey     nameTypeKey;
      NameKey         nameKey;
      UString         ustring;
  };
  class文件中,常量池項有很多種類,每一個常量池項的大小都不同,而對於常量池的使用又是如此之多,最好能夠使用數組來索引,這樣可以提高效率,所以KVM裡使用union來代表一個常池項,union的每一項是常量池項的一種可能的數據類型,這樣每一項都有了相同的大小,可以構造數組。

  

  顯然,這個數組就將是常量池的核心內容,那麼這個數組放在哪裡呢?就在下面這個結構中:

  struct constantPoolStruct {
      union constantPoolEntryStruct entries[1];
  };
  這就是常量池。這個常量池的設計很有意思:

  

  1、這個結構體中只有一個指針,指向一個常量池項體數組,數組中元素的個數是常量池項數+1,數組中的第一項(即序號為0的那一項)不是實際的常量池項體,而是存放了常量池項的數目,即表明了數組中接下來的元素數。要取得數組的長度信息,只有一個辦法,就是讀數組的第一個元素,為不造成空指針錯誤,所以constantPoolStruct在定義的時候就要保證數組的第0個元素必須存在,所以上面的entries在定義時就被指定為長度為1的數組。

  單純從數據結構的設計角度來看,我認為constantPoolStruct的設計並不是很清楚,使用數組的第一個無素來表示數組的長度多少一點顯得混亂,明明可以在constantPoolStruct的結構裡增加一個變量來表明數組長度,這樣不是更清楚嗎?之所以這樣做,我想也是與class文件中常量池的設計慣例有關。在class文件中, constant_pool緊跟在constant_pool_count之後,而constant_pool_count = constant_pool中實際的項數+1,相當於constant_pool_count也把自己當成了常量池中的第一項。

  

  由此可見,KVM的常量池設計與class文件如出一轍。

  

  2、常量池項體以一個union來表示,而union不帶有自身類型的信息,如何知道一個常量池項的類型呢?

  在一個class文件的常量池被載入後,生成了constantPoolStruct結構體的實例,在其中constantPoolEntryStruct數組的最後一項之後,一定會跟隨一個字節數組,這個數組中的每一個字節就是一個“常量池項頭”,長度與實際的常量池項數相同,即constant_pool_count-1,在這個字節中就指明了相應常量池項的類型。

  程序實現:

  構造常量池的代碼段主要在kvm/vmcommon/src/loader.c的loadConstantPool()函數中,函數原形如下:

  static POINTERLIST

  

  loadConstantPool(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass);

  兩個參數分別為類文件的句柄以及當前被載入類的指針。

  這個函數的總體流程如下:

  1- 循環讀取文件中常量池中所有項,把,把各項內容存入臨時數組RowPool中;(L649~L740)

  

  2- 計算常量池所占空間大小(以constantPoolEntryStruct枚舉體數計),並申請常量池空間;(L742~L757)

  3- 循環讀取暫存在RowPool中的常量信息,為常量池賦值。

  其中第2步值得一看,記算空間大小的那一行如下:

  

  int tableSize = numberOfEntries + ((numberOfEntries + (4 - 1)) >> 2);
  
   一個constantPoolEntryStruct枚舉體的大小為4,前面講過,在constantPoolEntryStruct數組的後要跟有一個字節數組來存放常量池項的類型信息,即每一個constantPoolEntryStruct要對應1個字節的常量池項頭,所以當以constantPoolEntryStruct枚舉體數為單位給常量池項頭數組申請空間時,需要向4字節對齊,每多1~4個常量池項頭,就要多申請一個constantPoolEntryStruct。這一句就是這個意思。

  loadConstantPool函數執行過程中,會把新生成的常量池指針賦給CurrentClass->constPool,這樣,這個類實例中就有完整的常量池了。


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