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

KVM的常量池

編輯:J2ME

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

Java是一種動態連接的語言,常量池的作用非常重要,常量池中除了包含代碼中所定義的各種基本類型(如intlong等等)和對象型(如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.cloadConstantPool()函數中,函數原形如下:

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