kvm link解析

之前的几篇文章介绍了 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 */
}

这里的步骤为:

  1. 沿着类的继承体系,向上查找,找到位于继承体系最高父类还没有link的,进行处理

  2. 链接父类

    1. 处理JavaLangObject

    2. 其他情况

    3. 如果父类是数组的话,则不能继承,抛出异常

    4. 如果父类是接口的话,则抛出异常

    5. 如果父类是final的话,则抛出异常

    6. 验证权限

    7. 如果该类是接口的话,则其父类必须是java.lang.Object

    8. 继承父类的instSize,为之后计算实例大小和实例字段打下基础

  3. 加载并连接接口

  4. 如果接口是数组,则抛出异常

  5. 如果接口所对应的状态为CLASS_ERROR,则抛出异常

  6. 如果接口所对应的状态为CLASS_LOADED,则抛出异常

  7. 如果该接口还没有load,则首先进行load

  8. 如果该接口还没有link,则抛出异常

  9. 如果该接口的访问标记中不含ACC_INTERFACE,则抛出异常

  10. 验证该类可以访问该接口

  11. 遍历当前类的实例字段,计算实例大小

  12. 将该类的状态修改为 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种情况是有权限访问的:

  1. 当前类为null
  2. 当前类等于targetClass
  3. targetClass的修饰符为public
  4. 二者属于同一个包

计算实例大小

在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规范是相同的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值