之前的几篇文章介绍了 kvm 将 class读取到kvm中的环节,此时该class的状态为CLASS_LOADED.此时其还需要进行链接,验证.本文介绍一下其链接环节.
链接
该部分的代码是在j2me_cldc/kvm/VmCommon/src/loader.c 中,代码如下:
// 该代码为loadClassfile的一部分
// 1. 沿着类的继承体系,向上查找,找到位于继承体系最高父类还没有link的,进行处理
while ((clazz = findSuperMostUnlinked(InitiatingClass)) != NULL) {
#if INCLUDEDEBUGCODE
if (traceclassloading || traceclassloadingverbose) {
fprintf(stdout, "Linking class: '%s'\n",
getClassName((CLASS)clazz));
}
#endif
// 2. 链接父类
if (clazz->superClass == NULL) {
// 2.1 处理JavaLangObject
if (clazz != JavaLangObject) {
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_BAD_SUPERCLASS);
}
clazz->instSize = 0;
}
else {// 2.2 其他情况
INSTANCE_CLASS superClass = clazz->superClass;
// 2.2.1 如果父类是数组的话,则不能继承,抛出异常
if (IS_ARRAY_CLASS((CLASS)superClass))
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_BAD_SUPERCLASS);
// 2.2.2 如果父类是接口的话,则抛出异常
if (superClass->clazz.accessFlags & ACC_INTERFACE) {
raiseExceptionWithMessage(IncompatibleClassChangeError,
KVM_MSG_CLASS_EXTENDS_INTERFACE);
}
// 2.2.3 如果父类是final的话,则抛出异常
if (superClass->clazz.accessFlags & ACC_FINAL) {
raiseExceptionWithMessage(VerifyError,
KVM_MSG_CLASS_EXTENDS_FINAL_CLASS);
}
// 2.2.4 验证权限
verifyClassAccess((CLASS)superClass,clazz);
// 2.2.5 如果该类是接口的话,则其父类必须是java.lang.Object
if (superClass != JavaLangObject &&
(clazz->clazz.accessFlags & ACC_INTERFACE)) {
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_BAD_SUPERCLASS);
}
/*
* Compute instance size and instance field offset.
* Make the instance size of the new class "inherit"
* the instance size of the superclass
*/
// 继承父类的instSize,为之后计算实例大小和实例字段打下基础
clazz->instSize = superClass->instSize;
}
// 3. 加载并连接接口
if (clazz->ifaceTable != NULL) {
unsigned int ifIndex;
for (ifIndex = 1; ifIndex <= (unsigned int)clazz->ifaceTable[0]; ifIndex++) {
INSTANCE_CLASS ifaceClass = (INSTANCE_CLASS)
clazz->constPool->entries
[clazz->ifaceTable[ifIndex]].clazz;
// 3.1 如果接口是数组,则抛出异常
if (IS_ARRAY_CLASS((CLASS)ifaceClass)) {
raiseExceptionWithMessage(ClassFormatError,
KVM_MSG_CLASS_IMPLEMENTS_ARRAY_CLASS);
}
// 3.2 如果接口所对应的状态为CLASS_ERROR,则抛出异常
if (ifaceClass->status == CLASS_ERROR) {
raiseException(NoClassDefFoundError);
}
// 3.3 如果接口所对应的状态为CLASS_LOADED,则抛出异常
else if (ifaceClass->status == CLASS_LOADED) {
raiseException(ClassCircularityError);
}
// 3.4 如果该接口还没有load,则首先进行load
else if (ifaceClass->status == CLASS_RAW) {
loadClassfile(ifaceClass, TRUE);
}
// 3.5 如果该接口还没有link,则抛出异常
else if (ifaceClass->status < CLASS_LINKED) {
fatalVMError(KVM_MSG_EXPECTED_CLASS_STATUS_GREATER_THAN_EQUAL_TO_CLASS_LINKED);
}
// 3.6 如果该接口的访问标记中不含ACC_INTERFACE,则抛出异常
if ((ifaceClass->clazz.accessFlags & ACC_INTERFACE) == 0) {
raiseExceptionWithMessage(IncompatibleClassChangeError,
KVM_MSG_CLASS_IMPLEMENTS_NON_INTERFACE);
}
// 3.7 验证该类可以访问该接口
verifyClassAccess((CLASS)ifaceClass, clazz);
}
}
// 4.遍历当前类的实例字段,计算实例大小
FOR_EACH_FIELD(thisField, clazz->fieldTable)
unsigned short accessFlags = (unsigned short)thisField->accessFlags;
if ((accessFlags & ACC_STATIC) == 0) {
thisField->u.offset = clazz->instSize;
clazz->instSize += (accessFlags & ACC_DOUBLE) ? 2 : 1;
}
END_FOR_EACH_FIELD
/* Move parts of the class to static memory */
// 此处为宏,默认情况下USESTATIC为0,则会在loader.c 中定义如下宏:
// define moveClassFieldsToStatic(CurrentClass)
moveClassFieldsToStatic(clazz);
// 将该类的状态修改为 CLASS_LINKED
clazz->status = CLASS_LINKED;
#if ENABLE_JAVA_DEBUGGER
if (vmDebugReady) {
CEModPtr cep = GetCEModifier();
cep->loc.classID = GET_CLASS_DEBUGGERID(&clazz->clazz);
cep->threadID = getObjectID((OBJECT)CurrentThread->javaThread);
cep->eventKind = JDWP_EventKind_CLASS_PREPARE;
insertDebugEvent(cep);
}
#endif
#if INCLUDEDEBUGCODE
if (traceclassloading || traceclassloadingverbose) {
fprintf(stdout, "Class linked ok\n");
}
#endif /* INCLUDEDEBUGCODE */
}
这里的步骤为:
-
沿着类的继承体系,向上查找,找到位于继承体系最高父类还没有link的,进行处理
-
链接父类
-
处理JavaLangObject
-
其他情况
-
如果父类是数组的话,则不能继承,抛出异常
-
如果父类是接口的话,则抛出异常
-
如果父类是final的话,则抛出异常
-
验证权限
-
如果该类是接口的话,则其父类必须是java.lang.Object
-
继承父类的instSize,为之后计算实例大小和实例字段打下基础
-
-
加载并连接接口
-
如果接口是数组,则抛出异常
-
如果接口所对应的状态为CLASS_ERROR,则抛出异常
-
如果接口所对应的状态为CLASS_LOADED,则抛出异常
-
如果该接口还没有load,则首先进行load
-
如果该接口还没有link,则抛出异常
-
如果该接口的访问标记中不含ACC_INTERFACE,则抛出异常
-
验证该类可以访问该接口
-
遍历当前类的实例字段,计算实例大小
-
将该类的状态修改为 CLASS_LINKED
我们将重点讲述第1, 2.2.4, 4 步.其他的步骤比较简单,结合上面的代码和注释就能理解.
向上查找未Link的父类
该步骤调用的方法为: findSuperMostUnlinked.其代码如下:
static INSTANCE_CLASS findSuperMostUnlinked(INSTANCE_CLASS clazz)
{
INSTANCE_CLASS result = NULL;
// 通过循环的方式,不断的向上查找未LINKED的类
while (clazz) {
if (clazz->status < CLASS_LINKED) {
result = clazz;
} else {
break;
}
clazz = clazz->superClass;
}
return result;
}
这里需要强调一点的是,在link的时候,会沿着类的继承体系不断向上找到未link的父类,进行link.
验证访问权限
这个步骤,是第2.2.4,3.7步所共同使用的.分别对应的情况为:在父子类的情况下,检查子类是否能够有权限访问(access)父类; 在实现接口的情况下,检查实现类是否有权限访问(access)接口.其代码如下:
void verifyClassAccess(CLASS targetClass, INSTANCE_CLASS currentClass)
{
if (!classHasAccessToClass(currentClass, targetClass)) {
START_TEMPORARY_ROOTS
DECLARE_TEMPORARY_ROOT(char *, targetName,
getClassName((CLASS)targetClass));
DECLARE_TEMPORARY_ROOT(char *, currentName,
getClassName((CLASS)currentClass));
sprintf(str_buffer,
KVM_MSG_CANNOT_ACCESS_CLASS_FROM_CLASS_2STRPARAMS,
targetName, currentName);
END_TEMPORARY_ROOTS
raiseExceptionWithMessage(IllegalAccessError, str_buffer);
}
}
这里主要是调用classHasAccessToClass方法进行检查,如果无权限的话,则抛出异常. classHasAccessToClass代码如下:
bool_t
classHasAccessToClass(INSTANCE_CLASS currentClass, CLASS targetClass) {
if ( currentClass == NULL
|| ((CLASS)currentClass == targetClass)
/* Note that array classes have the same package and access as
* their base classes */
|| (targetClass->accessFlags & ACC_PUBLIC)
|| (targetClass->packageName == currentClass->clazz.packageName)
) {
return TRUE;
} else {
return FALSE;
}
}
这里有4种情况是有权限访问的:
- 当前类为null
- 当前类等于targetClass
- targetClass的修饰符为public
- 二者属于同一个包
计算实例大小
在kvm中,分配内存时,是首先会分配父类的实例字段,然后再分配该类的实例字段。(静态字段分配在持久代.这点在之前的文章有介绍). 因此,这里的逻辑很简单.(比在openjdk中的字段分配采用的逻辑简单多了.)其代码如下:
FOR_EACH_FIELD(thisField, clazz->fieldTable)
unsigned short accessFlags = (unsigned short)thisField->accessFlags;
// 只计算实例字段
if ((accessFlags & ACC_STATIC) == 0) {
thisField->u.offset = clazz->instSize;
clazz->instSize += (accessFlags & ACC_DOUBLE) ? 2 : 1;
}
END_FOR_EACH_FIELD
在计算内存大小时,对于double,long 是需要占2个单位.(1个单位可以存放int,char,boolean,short,float).这点和jvm规范是相同的.