常量池项体中存放的就是对应的常量数据,比如各种数值型的常量或者字符串等等。
以下介绍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,这样,这个类实例中就有完整的常量池了。