这里再贴一下, loadRawClass 方法的步骤:
- 打开对应的class文件
- 加载版本号
- 加载常量池 <— 本文讲解该步骤.
- 加载类的标识符
- 加载接口
- 加载字段
- 加载方法
- 加载类的属性
- 关闭流
常量池的数据结构
常量池定义如下:
struct constantPoolStruct {
union constantPoolEntryStruct entries[1];
};
该数据结构稍微复杂一些。常量池中的第一个(下标为0)元素包含常量池中的条目数。
实际常量池条目位于条目[1]——条目[length-1]中。
最后一个常量池条目[长度-1]之后紧接着是一个字节数组,其中包含指示每个常量池条目类型的标记.如图所示:
加载常量池
加载常量池的方法为:loadConstantPool,位于 j2me_cldc/kvm/VmCommon/src/loader.c. 代码如下,有删减:
static POINTERLIST
loadConstantPool(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass)
{
unsigned short constantCount = loadShort(ClassFileH);
CONSTANTPOOL ConstantPool;
int cpIndex;
POINTERLIST result;
int lastNonUtfIndex = -1;
// 1. 声明要使用的RawPool, StringPool
START_TEMPORARY_ROOTS
/*
* CONSTANTPOOL_ENTRY RawPool = (
RawPool = (CONSTANTPOOL_ENTRY)mallocBytes(constantCount * (1 + sizeof(*RawPool))), \
TemporaryRoots[TemporaryRootsLength++].cellp = (cell *)&RawPool,
RawPool);
*/
DECLARE_TEMPORARY_ROOT(CONSTANTPOOL_ENTRY, RawPool,
(CONSTANTPOOL_ENTRY)mallocBytes(constantCount * (1 + sizeof(*RawPool))));// 1 + sizeof(*RawPool) 是为了分配标记数组,看CONSTANTPOOL的注释
/* 由于在后续流程中需要使用常量池中的信息,如类名,方法标识符,因此,将这些信息保存在StringPool 中
* POINTERLIST StringPool = (
StringPool = (POINTERLIST)callocObject((((sizeof(struct pointerListStruct) + 3) >> 2)+((constantCount)-1)), GCT_POINTERLIST), \
TemporaryRoots[TemporaryRootsLength++].cellp = (cell *)&StringPool, \
StringPool);
*/
DECLARE_TEMPORARY_ROOT(POINTERLIST, StringPool,
(POINTERLIST)callocObject(SIZEOF_POINTERLIST(constantCount), GCT_POINTERLIST));
StringPool->length = constantCount;
#define RAW_POOL(i) (RawPool[i])
#define CP_ENTRY(i) (ConstantPool->entries[i])
/* 2. 读取常量池元素 */
for (cpIndex = 1; cpIndex < constantCount; cpIndex++) {
unsigned char tag = loadByte(ClassFileH);
unsigned char *Tags = (unsigned char *)(&RawPool[constantCount]);
// 2.1 将tag分配在常量池条目的最后
Tags[cpIndex] = tag;
switch (tag) {
case CONSTANT_String:
case CONSTANT_Class: {
/* A single 16-bit entry that points to a UTF string */
unsigned short nameIndex = loadShort(ClassFileH);
RAW_POOL(cpIndex).integer = nameIndex;
break;
}
.... 省略其他代码
default:
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_INVALID_CONSTANT_POOL_ENTRY);
break;
}
if (tag != CONSTANT_Utf8) {
lastNonUtfIndex = cpIndex;
}
}
{
/*
* 3. 分配内存,其大小为所有的entries + tag
*/
int numberOfEntries = lastNonUtfIndex + 1;
int tableSize =
numberOfEntries + ((numberOfEntries + (CELL - 1)) >> log2CELL); // 对齐
#if USESTATIC
IS_TEMPORARY_ROOT(ConstantPool,
(CONSTANTPOOL)callocObject(tableSize, GCT_NOPOINTERS));
#else
ConstantPool = (CONSTANTPOOL)callocPermanentObject(tableSize);
#endif
CONSTANTPOOL_LENGTH(ConstantPool) = numberOfEntries;
CurrentClass->constPool = ConstantPool;
}
/* 4. 创建常量池元素,复制数据 */
for (cpIndex = 1; cpIndex < constantCount; cpIndex++) {
/* These can move between iterations of the loop */
unsigned char *Tags = (unsigned char *)(&RawPool[constantCount]);
unsigned char *CPTags = CONSTANTPOOL_TAGS(ConstantPool);// ((unsigned char *)&(ConstantPool)->entries[CONSTANTPOOL_LENGTH(ConstantPool)])
unsigned char tag = Tags[cpIndex];
if (cpIndex <= lastNonUtfIndex) {
CPTags[cpIndex] = tag;
}
switch (tag) {
case CONSTANT_Class: {
unsigned short nameIndex =
(unsigned short)RAW_POOL(cpIndex).integer;
START_TEMPORARY_ROOTS
DECLARE_TEMPORARY_ROOT(const char *, name,
getUTF8String(&StringPool, nameIndex));
verifyName(name, LegalClass);
CP_ENTRY(cpIndex).clazz =
getRawClassX(&name, 0, strlen(name));
END_TEMPORARY_ROOTS
break;
}
case CONSTANT_String: {
unsigned short nameIndex =
(unsigned short)RAW_POOL(cpIndex).integer;
char *name = getUTF8String(&StringPool, nameIndex);
INTERNED_STRING_INSTANCE string =
internString(name, strlen(name));
CP_ENTRY(cpIndex).String = string;
break;
}
.... 省略其他代码
default:
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_INVALID_CONSTANT_POOL_ENTRY);
break;
}
}
result = StringPool;
END_TEMPORARY_ROOTS
return result;
}
其步骤如下:
- 声明要使用的RawPool, StringPool。由于在后续流程中需要使用常量池中的信息,如类名,方法标识符,因此,将这些信息保存在StringPool 中。
- 读取常量池元素
- 在内存中为ConstantPool分配内存,其大小为所有的entries + tag
- 复制数据
其中,重点是在 第2,4步.我们分开来了.
读取常量元素
再读取元素时,首先,将常量池元素的tag保存在RawPool的后面.代码如下:
unsigned char tag = loadByte(ClassFileH);
unsigned char *Tags = (unsigned char *)(&RawPool[constantCount]);
Tags[cpIndex] = tag;
接下来,根据常量池的元素,就不同的判断. 在JVM规范中,对常量池项的tag的定义是:
CONSTANT_String,CONSTANT_Class
代码如下:
case CONSTANT_String:
case CONSTANT_Class: {
/* A single 16-bit entry that points to a UTF string */
unsigned short nameIndex = loadShort(ClassFileH);
RAW_POOL(cpIndex).integer = nameIndex;
break;
}
因此,在 RAW_POOL(cpIndex) 中保存的是CONSTANT_String, CONSTANT_Class所对应的下标,而在RAW_POOL的后面,在对应下标的位置分别保存的是8,7.
CONSTANT_Fieldref,CONSTANT_Methodref,CONSTANT_InterfaceMethodref
代码如下:
case CONSTANT_Fieldref:
case CONSTANT_Methodref:
case CONSTANT_InterfaceMethodref: {
/* Two 16-bit entries */
unsigned short classIndex = loadShort(ClassFileH);
unsigned short nameTypeIndex = loadShort(ClassFileH);
RAW_POOL(cpIndex).method.classIndex = classIndex;
RAW_POOL(cpIndex).method.nameTypeIndex = nameTypeIndex;
break;
}
因此,在RAW_POOL(cpIndex) 中保存的是三者的class的下标,标识符的下标.而在RAW_POOL的后面,在对应下标的位置分别保存的是9,10,11.
CONSTANT_Float,CONSTANT_Integer
case CONSTANT_Float:
#if !IMPLEMENTS_FLOAT
fatalError(KVM_MSG_FLOATING_POINT_NOT_SUPPORTED);
#endif
case CONSTANT_Integer:
{
/* A single 32-bit value */
long value = loadCell(ClassFileH);
RAW_POOL(cpIndex).integer = value;
break;
}
因此,在RAW_POOL(cpIndex)中保存的是float,int的值.而在RAW_POOL的后面,在对应下标的位置分别保存的是4,3.
CONSTANT_Double,CONSTANT_Long
case CONSTANT_Double:
#if !IMPLEMENTS_FLOAT
fatalError(KVM_MSG_FLOATING_POINT_NOT_SUPPORTED);
#endif
case CONSTANT_Long:
/* A 64-bit value */
RAW_POOL(cpIndex).integer = loadCell(ClassFileH);
cpIndex++;
if (cpIndex >= constantCount) {
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_BAD_64BIT_CONSTANT);
}
/* set this location in the Tags array to 0 so it's
* flagged as a bogus location if some TCK test decides
* to read from the middle of this long constant pool entry.
* 将标记数组中的这个位置设置为0,这样,如果某个TCK测试决定从这个长常量池条目的中间读取,它就会被标记为一个伪位置
*/
Tags[cpIndex] = 0;
RAW_POOL(cpIndex).integer = loadCell(ClassFileH);
break;
double,long是64位,占两个字节,在常量池中占两个位置.而在RAW_POOL的后面,在对应下标的位置分别保存的是6,5.
CONSTANT_NameAndType
case CONSTANT_NameAndType: {
// 常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,这个结构要么表示特殊的方法名<init>(§2.9),要么表示一个有效的字段或方法的非限定名(Unqualified Name)。
unsigned short nameIndex = loadShort(ClassFileH);
// 常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,这个结构表示一个有效的字段描述符(§4.3.2)或方法描述符(§4.3.3)。
unsigned short typeIndex = loadShort(ClassFileH);
/* In the second pass, below, these will be replaced with the
* actual nameKey and typeKey. Currently, they are indices
* to items in the constant pool that may not yet have been
* loaded */
RAW_POOL(cpIndex).nameTypeKey.nt.nameKey = nameIndex;
RAW_POOL(cpIndex).nameTypeKey.nt.typeKey = typeIndex;
break;
}
因此,RAW_POOL中保存的是nameIndex, typeIndex的下标,而在RAW_POOL的后面,在对应下标的位置分别保存的是12
CONSTANT_Utf8
case CONSTANT_Utf8: {
unsigned short length = loadShort(ClassFileH);
/* This allocation may invalidate ClassFile */
char *string = mallocBytes(length + 1);
STRING_POOL(cpIndex) = string;
loadBytes(ClassFileH, string, length);
string[length] = '\0';
verifyUTF8String(string, length);
}
这里和之前的情况不一样,将Utf8所代表的内容,保存在STRING_POOL中.而在RAW_POOL的后面,在对应下标的位置分别保存的是8.
复制数据
由于之前的环节,只是将常量池元素原封不动的读取,因此,这步将常量池元素进行解析.比如,对于CONSTANT_Class,之前只是保存的是name_index的值,而常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,代表一个有效的类或接口二进制名称的内部形式(也就是字符串).
经过这步处理后,常量池中保存的就是一个有效的类或接口二进制名称的内部形式(也就是字符串).
CONSTANT_Class
case CONSTANT_Class: {
unsigned short nameIndex =
(unsigned short)RAW_POOL(cpIndex).integer;
START_TEMPORARY_ROOTS
DECLARE_TEMPORARY_ROOT(const char *, name,
getUTF8String(&StringPool, nameIndex));
verifyName(name, LegalClass);
CP_ENTRY(cpIndex).clazz =
getRawClassX(&name, 0, strlen(name));
END_TEMPORARY_ROOTS
break;
}
关于这部分上文有讲述.
CONSTANT_String
case CONSTANT_String: {
unsigned short nameIndex =
(unsigned short)RAW_POOL(cpIndex).integer;
char *name = getUTF8String(&StringPool, nameIndex);
INTERNED_STRING_INSTANCE string =
internString(name, strlen(name));
CP_ENTRY(cpIndex).String = string;
break;
}
这里将ConstantPool中的值该为指向在kvm内部的InternStringTable中,保存该字符串的指针
CONSTANT_Fieldref,CONSTANT_Methodref,CONSTANT_InterfaceMethodref
case CONSTANT_Fieldref:
case CONSTANT_Methodref:
case CONSTANT_InterfaceMethodref: {
/* Two 16-bit entries */
unsigned short classIndex = RAW_POOL(cpIndex).method.classIndex;
unsigned short nameTypeIndex =RAW_POOL(cpIndex).method.nameTypeIndex;
// 进行校验
if (classIndex >= constantCount ||
Tags[classIndex] != CONSTANT_Class ||
nameTypeIndex >= constantCount ||
Tags[nameTypeIndex] != CONSTANT_NameAndType) {
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_BAD_FIELD_OR_METHOD_REFERENCE);
} else {
unsigned short nameIndex =
RAW_POOL(nameTypeIndex).nameTypeKey.nt.nameKey;
unsigned short typeIndex =
RAW_POOL(nameTypeIndex).nameTypeKey.nt.typeKey;
char* name = getUTF8String(&StringPool, nameIndex);
char* type = getUTF8String(&StringPool, typeIndex);
if ( (tag == CONSTANT_Fieldref && type[0] == '(')
|| (tag != CONSTANT_Fieldref && type[0] != '(')
|| (tag != CONSTANT_Fieldref &&
!strcmp(name, "<clinit>"))) {
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_BAD_NAME_OR_TYPE_REFERENCE);
}
CP_ENTRY(cpIndex) = RAW_POOL(cpIndex);
}
break;
}
这里是直接复制RAW_POOL保存的值,也就是保存的是三者的class的下标,标识符的下标.
CONSTANT_Double,CONSTANT_Long,CONSTANT_Float,CONSTANT_Integer
case CONSTANT_Double:
#if !IMPLEMENTS_FLOAT
fatalError(KVM_MSG_FLOATING_POINT_NOT_SUPPORTED);
#endif
case CONSTANT_Long:
CP_ENTRY(cpIndex).integer = RAW_POOL(cpIndex).integer;
cpIndex++;
CPTags[cpIndex] = 0;
CP_ENTRY(cpIndex).integer = RAW_POOL(cpIndex).integer;
break;
case CONSTANT_Float:
#if !IMPLEMENTS_FLOAT
fatalError(KVM_MSG_FLOATING_POINT_NOT_SUPPORTED);
#endif
case CONSTANT_Integer:
CP_ENTRY(cpIndex).integer = RAW_POOL(cpIndex).integer;
break;
这里是直接复制RAW_POOL保存的值.
CONSTANT_NameAndType
case CONSTANT_NameAndType: {
unsigned short nameIndex = RAW_POOL(cpIndex).nameTypeKey.nt.nameKey;
unsigned short typeIndex = RAW_POOL(cpIndex).nameTypeKey.nt.typeKey;
START_TEMPORARY_ROOTS
DECLARE_TEMPORARY_ROOT(const char *, name,
getUTF8String(&StringPool, nameIndex));
DECLARE_TEMPORARY_ROOT(const char *, type,
getUTF8String(&StringPool, typeIndex));
NameKey nameKey;
unsigned short typeKey;
if (type[0] == '(') {
verifyName(name, LegalMethod);
verifyMethodType(name, type);
typeKey = change_MethodSignature_to_Key(&type, 0,
strlen(type));
} else {
verifyName(name, LegalField);
verifyFieldType(type);
typeKey = change_FieldSignature_to_Key(&type, 0,
strlen(type));
}
nameKey = change_Name_to_Key(&name, 0, strlen(name));
CP_ENTRY(cpIndex).nameTypeKey.nt.nameKey = nameKey;
CP_ENTRY(cpIndex).nameTypeKey.nt.typeKey = typeKey;
END_TEMPORARY_ROOTS
break;
}
对于CONSTANT_NameAndType,其在class的结构如下:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
- name_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,这个结构要么表示特殊的方法名<init>(§2.9),要么表示一个有效的字段或方法的非限定名(Unqualified Name)。
- descriptor_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,这个结构表示一个有效的字段描述符(§4.3.2)或方法描述符(§4.3.3)。
因此,这里将ConstantPool中的值修改为nameKey, typeKey. 这里涉及的方法为change_MethodSignature_to_Key, change_FieldSignature_to_Key.比较简单,读者可自行查看.
CONSTANT_Utf8
case CONSTANT_Utf8:
/* We don't need these after loading time. So why bother */
if (cpIndex <= lastNonUtfIndex) {
CP_ENTRY(cpIndex).integer = 0;
CPTags[cpIndex] = 0;
}
break;
对于该类型的元素而言,由于将常量池元素解析完毕后,该元素也就没用了,但是为了占位,也就将其值修改为0.