本文讲述kvm加载字段的部分.在jvm规范中,每个字段(Field)都由field_info结构所定义,在同一个Class文件中,不会有两个字段同时具有相同的字段名和描述符.field_info结构格式如下:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count]; }
field_info结构各项的说明如下:
-
access_flags
access_flags项的值是用于定义字段被访问权限和基础属性的掩码标志。access_flags的取值范围和相应含义如图:
字段如果带有ACC_SYNTHETIC标志,则说明这个字段不是由源码产生的,而是由编译器自动产生的。字段如果被标有ACC_ENUM标志,这说明这个字段是一个枚举类型成员。
Class文件中的字段可以被设置多个上图中的标记。不过有些标记是互斥的,一个字段最多只能设置ACC_PRIVATE, ACC_PROTECTED,和ACC_PUBLIC(JLS §8.3.1)三个标志中的一个,也不能同时设置标志ACC_FINAL和ACC_VOLATILE(JLS §8.3.1.4)。
接口中的所有字段都具有ACC_PUBLIC,ACC_STATIC和ACC_FINAL标记,也可能被设置ACC_SYNTHETIC标记,但是不能含有上图中的其它符号标记了(JLS §9.3)。 在上图中没有出现的access_flags项的值为扩充而预留,在生成的Class文件中应被设置成0,Java虚拟机实现也应该忽略它们。
而在KVM中,识别的访问标记中不包含ACC_SYNTHETIC,ACC_ENUM
-
name_index
name_index项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,表示一个有效的字段的非全限定名(§4.2.2)。
-
descriptor_index
descriptor_index项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,表示一个有效的字段的描述符(§4.3.2)。
-
attributes_count
attributes_count的项的值表示当前字段的附加属性(§4.7)的数量。
-
attributes[]
attributes表的每一个成员的值必须是attribute(§4.7)结构,一个字段可以有任意个关联属性。 本规范所定义的field_info结构中,attributes表可出现的成员有:
ConstantValue(§4.7.2), Synthetic(§4.7.8), Signature(§4.7.9), Deprecated(§4.7.15), RuntimeVisibleAnnotations(§4.7.16) 和 RuntimeInvisibleAnnotations(§4.7.17)。
Java虚拟机实现必须正确的识别和读取field_info结构的attributes表中的ConstantValue(§4.7.2)属性。如果Java虚拟机实现支持版本号为49.0或更高的Class文件,那么它必须正确的识别和读取这些Class文件中的Signature(§4.7.9), RuntimeVisibleAnnotations(§4.7.16) 和RuntimeInvisibleAnnotations(§4.7.17)结构。 所有Java虚拟机实现都必须默认忽略field_info结构中attributes表所不可识别的成员。本规范中没有定义的属性不可影响Class文件的语义,它们只能提供附加描述信息(§4.7.1)。
而在KVM中,只处理了ConstantValue.
加载字段
在KVM 中加载字段的实现,代码量比较大,其步骤如下:
-
计算要分配内存的大小.如果字段数量为0,则不进行后续处理.其中,计算内存大小的代码如下:
unsigned short fieldCount = loadShort(ClassFileH); // ((sizeof(struct fieldTableStruct) + 3) >> 2 + (n - 1) * (sizeof(struct fieldStruct) + 3) >> 2) int fieldTableSize = SIZEOF_FIELDTABLE(fieldCount);
fieldTableStruct的定义如下:
struct fieldTableStruct { long length; // 存放字段表的数量 struct fieldStruct fields[1]; };
fieldStruct 的定义如下:
/* FIELD */ struct fieldStruct { NameTypeKey nameTypeKey; long accessFlags; /* 访问标记*/ INSTANCE_CLASS ofClass; /* 指向该对象的实例 */ union { long offset; /* 非静态字段使用 */ void *staticAddress; /* 静态字段才使用 */ } u; };
-
在内存中进行分配:
#if USESTATIC fieldTable = (FIELDTABLE)callocObject(fieldTableSize, GCT_NOPOINTERS); #else fieldTable = (FIELDTABLE)callocPermanentObject(fieldTableSize); #endif fieldTable->length = fieldCount; CurrentClass->fieldTable = fieldTable;
-
复制数据
-
读取access_flags
-
读取name_index
-
读取descriptor_index
-
如果是静态的话,则尝试读取其ConstantValue.并将其保存至fieldStruct.u.offset中.代码如下:
static void loadStaticFieldAttributes(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass, FIELD thisField, POINTERLIST_HANDLE StringPoolH) { int cpIndex = 0; unsigned short attrCount = loadShort(ClassFileH); unsigned short attrIndex; /* See if the field has any attributes in the class file */ for (attrIndex = 0; attrIndex < attrCount; attrIndex++) { unsigned short attrNameIndex = loadShort(ClassFileH); unsigned long attrLength = loadCell(ClassFileH); const char* attrName = getUTF8String(StringPoolH, attrNameIndex); /* Check if the attribute represents a constant value index */ if (strcmp(attrName, "ConstantValue") == 0) { if (attrLength != 2) { raiseExceptionWithMessage(ClassFormatError, KVM_MSG_BAD_CONSTANTVALUE_LENGTH); } if (cpIndex != 0) { raiseExceptionWithMessage(ClassFormatError, KVM_MSG_DUPLICATE_CONSTANTVALUE_ATTRIBUTE); } /* Read index to a constant in constant pool */ cpIndex = loadShort(ClassFileH); if (cpIndex == 0) { raiseExceptionWithMessage(ClassFormatError, KVM_MSG_BAD_CONSTANT_INDEX); } } else { /* Unrecognized attribute; read the bytes to /dev/null */ skipBytes(ClassFileH, attrLength); } } thisField->u.offset = cpIndex; }
这部分的代码如下:
// 3. 复制数据 for (index = 0; index < fieldCount; index++) { // 读取access\_flags unsigned short accessFlags = loadShort(ClassFileH) & RECOGNIZED_FIELD_FLAGS; // 读取name\_index unsigned short nameIndex = loadShort(ClassFileH); // 读取 descriptor\_index unsigned short typeIndex = loadShort(ClassFileH); bool_t isStatic = (accessFlags & ACC_STATIC) != 0; FIELD thisField; START_TEMPORARY_ROOTS DECLARE_TEMPORARY_ROOT(const char *, fieldName, getUTF8String(StringPoolH, nameIndex)); DECLARE_TEMPORARY_ROOT(const char *, signature, getUTF8String(StringPoolH, typeIndex)); NameTypeKey result; verifyFieldFlags(accessFlags, CurrentClass->clazz.accessFlags); verifyName(fieldName, LegalField); verifyFieldType(signature); // 计算nameKey result.nt.nameKey = change_Name_to_Key(&fieldName, 0, strlen(fieldName)); // 计算typeKey result.nt.typeKey = change_FieldSignature_to_Key(&signature, 0, strlen(signature)); ASSERTING_NO_ALLOCATION thisField = &CurrentClass->fieldTable->fields[index]; #if INCLUDEDEBUGCODE if (traceclassloadingverbose) { fprintf(stdout, "Field '%s' loaded\n", fieldName); } #endif /* INCLUDEDEBUGCODE */ /* Check if the field is double length, or is a pointer type. * If so set the appropriate bit in the word */ switch (signature[0]) { case 'D': case 'J': accessFlags |= ACC_DOUBLE; break; case 'L': case '[': accessFlags |= ACC_POINTER; break; } /* 保存变量 */ thisField->nameTypeKey = result; thisField->ofClass = CurrentClass; thisField->accessFlags = accessFlags; if (isStatic) { // 如果是静态的话,则尝试读取其ConstantValue.并将其保存至fieldStruct.u.offset中 loadStaticFieldAttributes(ClassFileH, CurrentClass, thisField, StringPoolH); if (accessFlags & ACC_POINTER) { staticPtrCount++; } else { staticNonPtrCount += (accessFlags & ACC_DOUBLE) ? 2 : 1; } } else { ignoreAttributes(ClassFileH, StringPoolH); } END_ASSERTING_NO_ALLOCATION END_TEMPORARY_ROOTS }
这里有一点要说一下:
在KVM中,会将静态字段分配在一个持久代,其数据结构为: POINTERLIST.而在KVM,静态字段又分为静态指针字段,静态非指针字段.而在分配时,就为一个技巧,将静态指针字段分配在静态非指针字段的前面,并且将POINTERLIST的长度设置为静态指针字段的数量,这样在垃圾收集的时候,就会只收集静态指针字段.
-
-
处理静态字段。
if (staticPtrCount > 0 || staticNonPtrCount > 0) { // 1. 计算要分配的大小,并进行分配. int staticsSize = SIZEOF_POINTERLIST(staticNonPtrCount+staticPtrCount); POINTERLIST statics = (POINTERLIST)callocPermanentObject(staticsSize); // 所有静态指针,分配在非指针的静态字段的前面 void **nextPtrField = (void **)statics->data; void **nextNonPtrField = nextPtrField + staticPtrCount; /* 将POINTERLIST的长度设置为静态指针字段的数量,这样在垃圾收集的时候,就会只收集静态指针字段 */ statics->length = staticPtrCount; CurrentClass->staticFields = statics; ASSERTING_NO_ALLOCATION CONSTANTPOOL ConstantPool = CurrentClass->constPool; if (USESTATIC) { /* Otherwise, this is in permanent memory and won't move */ fieldTable = CurrentClass->fieldTable; } // 处理静态字段 FOR_EACH_FIELD(thisField, fieldTable) long accessFlags = thisField->accessFlags; unsigned short cpIndex; // 如果该字段不是静态,则跳过 if (!(accessFlags & ACC_STATIC)) { continue; } cpIndex = (unsigned short)(thisField->u.offset); if (thisField->accessFlags & ACC_POINTER) { /* 这种情况只能是string,这里是对string的特殊处理,对于其他对象,数组,都是在<cinit>方法中初始化的*/ thisField->u.staticAddress = nextPtrField; if (cpIndex != 0) { verifyConstantPoolEntry(CurrentClass, cpIndex, CONSTANT_String); *(INTERNED_STRING_INSTANCE *)nextPtrField = CP_ENTRY(cpIndex).String; } nextPtrField++; } else { thisField->u.staticAddress = nextNonPtrField; if (cpIndex != 0) { unsigned char tag; switch(thisField->nameTypeKey.nt.typeKey) { case 'B': case 'C': case 'Z': case 'S': case 'I': tag = CONSTANT_Integer; break; case 'F': #if !IMPLEMENTS_FLOAT fatalError(KVM_MSG_FLOATING_POINT_NOT_SUPPORTED); #endif tag = CONSTANT_Float; break; case 'D': #if !IMPLEMENTS_FLOAT fatalError(KVM_MSG_FLOATING_POINT_NOT_SUPPORTED); #endif tag = CONSTANT_Double; break; case 'J': tag = CONSTANT_Long; break; default: raiseExceptionWithMessage(ClassFormatError, KVM_MSG_BAD_SIGNATURE); } verifyConstantPoolEntry(CurrentClass, cpIndex, tag); // 设置静态变量的值 if (accessFlags & ACC_DOUBLE) { /* Initialize a double or long */ CONSTANTPOOL_ENTRY thisEntry = &CP_ENTRY(cpIndex); unsigned long hiBytes, loBytes; hiBytes = (unsigned long)(thisEntry[0].integer); loBytes = (unsigned long)(thisEntry[1].integer); SET_LONG_FROM_HALVES(nextNonPtrField, hiBytes, loBytes); } else { *(cell *)nextNonPtrField = CP_ENTRY(cpIndex).integer; } } nextNonPtrField += (accessFlags & ACC_DOUBLE) ? 2 : 1; } END_FOR_EACH_FIELD END_ASSERTING_NO_ALLOCATION }
-
检查是否有重复字段
if (fieldCount >= 2) { if (USESTATIC) { /* Otherwise, this is in permanent memory and won't move */ fieldTable = CurrentClass->fieldTable; } ASSERTING_NO_ALLOCATION /* Check to see if there are two fields with the same name/type */ FIELD firstField = &fieldTable->fields[0]; FIELD lastField = firstField + (fieldCount - 1); FIELD outer, inner; for (outer = firstField; outer < lastField; outer++) { for (inner = outer + 1; inner <= lastField; inner++) { // 如果有相同的,则抛出异常 if (outer->nameTypeKey.i == inner->nameTypeKey.i) { raiseExceptionWithMessage(ClassFormatError, KVM_MSG_DUPLICATE_FIELD_FOUND); } } } END_ASSERTING_NO_ALLOCATION }
总结一下:
kvm中定义了fieldTableStruct,用来保存字段的信息.同时,对应静态字段,又分配了大小为staticNonPtrCount+staticPtrCount的POINTERLIST.并且由CurrentClass中的staticFields指向该POINTERLIST.代码如下:
CurrentClass->staticFields = statics;
对于这个POINTERLIST使用了一个技巧,所有静态指针分配在非指针的静态字段的前面。这样在垃圾收集的时候,就会只收集静态指针字段.
而对于非静态字段,只是保存了nameTypeKey,ofClass,accessFlags。代码如下:
thisField->nameTypeKey = result;
thisField->ofClass = CurrentClass;
thisField->accessFlags = accessFlags;