在JAVA虚拟机规范(JAVA SE 7 )中,针对初始化的过程有如下的定义:

每个类或接口C,都有一个唯一的初始化锁LC。如何实现从C到LC的映射可由Java虚拟机实现自行决定。例如,LC可以是C的Class对象,或者是与Class对象相关的管程(Monitor)。初始化C的过程如下:

  • 同步C的初始化锁LC。这个操作会导致当前线程一直等待直到可以获得LC锁
  • 如果C的Class对象显示当前C的初始化是由其它线程正在进行,那么当前线程释放LC并进入阻塞状态,直到它知道初始化工作已经由其它线程完成,那么当前线程在此重试此步骤。
  • 如果C的 Class对象显示C的初始化正由当前线程在做,这就是对初始化的递归请求。释放LC并正常返回。
  • 如果C的Class对象显示Class已经被初始化完成,那么什么也不做。释放LC并正常返回。
  • 如果C的Class对象显示它处于一个错误的状态,就不再初始化了。释放LC并抛出NoClassDefFoundError异常。
  • 否则,记录下当前线程正在初始化C的Class对象,随后释放LC。根据属性出现在ClassFile的顺序,利用常量池中的ConstantValue属性(§4.7.2)来初始化C中的各个final static字段。
  • 接下来,如果C是类而不是接口,而且它的父类SC还没有被初始化过,那就对于SC也进行完整的初始化过程。当然如果必要的话,需要先验证和准备SC。如果在初始化SC的时候因为抛出异常而中断,那么就获取LC后将C的Class对象标识为错误状态,并通知所有正在等待的线程,最后释放LC并异常退出,抛出与SC初始化遇到的异常相同的异常。
  • 之后,通过查询C的定义加载器来决定是否为C开启断言机制。
  • 执行C的类或接口初始化方法。
  • 如果正常地执行了类或接口的初始化方法,之后就请求获取LC,标记C的Class对象已经被完全初始化,通知所有正在等待的线程,接着释放LC,正常地退出整个过程。
  • 否则,类或接口的初始化方法就必须抛出一个异常E并中断退出。如果E不是Error或它的某个子类,那就创建一个新的ExceptionInInitializerError实例,然后将此实例作为E的参数,之后的步骤就使用E这个对象。如果因为OutOfMemoryError问题而不能创建ExceptionInInitializerError实例,那在之后就使用OutOfMemoryError异常对象作为E的参数。
  • 获取LC,标记下C的Class对象有错误发生,通知所有的等待线程,释放LC,将E或上述的具体错误对象作为此次意外中断的原因。

runClinit

而在KVM中的runClinit也是按照以上的规范来进行的.其代码一开始会获得初始化的状态和要实例化的class.代码如下:

INSTANCE_CLASS thisClass;
int state;
bool_t haveMonitor = FALSE;
if (exceptionFrameH) {
    runClinitException(exceptionFrameH);
    return;
}

state = topStackAsType(cell);
thisClass = secondStackAsType(INSTANCE_CLASS);

针对我们的案例,此处获得的值分别为1,JavaLangClass.如图所示:
在这里插入图片描述

由于此处状态为1,因此会执行如下代码:

if (!OBJECT_HAS_MONITOR(&thisClass->clazz)
        && thisClass->initThread == NULL) {
    goto markClass;// 此处执行这里
}

/* Step 1:  Grab the class monitor so we have exclusive access. 同步C的初始化锁LC。这个操作会导致当前线程一直等待直到可以获得LC锁。  */
if (monitorEnter((OBJECT)thisClass) != MonitorStatusOwn) { // 如果有其他线程在初始化,则等待
    /* We've been forced to wait.  When we're awoken, we'll have
     * the lock */
    topStack = 2;
    return;
} else {
    /* FALL THROUGH.  We have the lock */
}

由于此时JavaLangClass的initThread为null,因此会执行markClass处的代码.如下:

markClass:
        if (thisClass->initThread == CurrentThread ||
                   thisClass->status == CLASS_READY) { /* step 4 */
            /* Step 3, Step 4:
             * This thread is already initializing the class, or the class
             * has somehow already become initialized.  We're done.
             * 如果C的 Class对象显示C的初始化正由当前线程在做,这就是对初始化的递归请求。释放LC并正常返回。
             * 如果C的Class对象显示Class已经被初始化完成,那么什么也不做。释放LC并正常返回。
             */
            if (haveMonitor) {
                char *junk;
                monitorExit((OBJECT)thisClass, &junk);
            }
            popFrame();
            return;
        }
        if (thisClass->status == CLASS_ERROR) {
            /* Step 5:
             * What can we do?
             * 如果C的Class对象显示它处于一个错误的状态,就不再初始化了。释放LC并抛出NoClassDefFoundError异常。
             */
            if (haveMonitor) {
                char *junk;
                monitorExit((OBJECT)thisClass, &junk);
            }
            popFrame();
            return;
        }

        /* Step 6:
         * Mark that we're about to initialize this class 记录下当前线程正在初始化C的Class对象,随后释放LC*/
        setClassInitialThread(thisClass, CurrentThread);// 此处执行这里
        if (haveMonitor) {
            char *junk;
            monitorExit((OBJECT)thisClass, &junk);
            haveMonitor = FALSE;
        }
        /* FALL THROUGH */

此处重点是执行setClassInitialThread,代码如下:

#define setClassInitialThread(iclazz, thread) \
                            ((iclazz)->initThread = (thread))

注意,此处的CurrentThread为MainThread,这点在kvm启动流程-006中有介绍.

接下来执行如下代码:

if ((thisClass->clazz.accessFlags & ACC_INTERFACE) == 0) {
    INSTANCE_CLASS superClass = thisClass->superClass;
    if (superClass && superClass->status != CLASS_READY) {
        topStack = 4;
        initializeClass(superClass);
        return;
    }
}

由于class的父类为object,且一直没有初始化完毕,即其状态不是CLASS_READY,因此此处会首先将class的状态改为4,然后初始化object.此时的情况如下:

在这里插入图片描述

而在initializeClass中,由于object不存在父类,因此会执行如下代码:

if ((thisClass->superClass == NULL ||
    thisClass->superClass->status == CLASS_READY) &&
    getSpecialMethod(thisClass,clinitNameAndType) == NULL) {
    setClassStatus(thisClass,CLASS_READY);
}

之后,一路return,返回到Interpret的那个大switch中.代码如下:

#if INFREQUENTSTANDARDBYTECODES
SELECT(CUSTOMCODE)      // 宏展开为 case CUSTOMCODE: {                        /* 0xDF */
        cell *stack = (cell*)(fp + 1);// 取得栈底
        CustomCodeCallbackFunction func = *(CustomCodeCallbackFunction *)stack;// 取得对应的函数
        
        // 如果函数存在,则执行之
        if (func != NULL) {
            VMSAVE
            func(NULL);
            VMRESTORE
        }
        else {
            POP_FRAME
        }
DONE_R // 宏展开为 } goto reschedulePoint;
#endif

这点注意的是,在执行runClinit执行前后,分别使用了VMSAVE,VMRESTORE将ip,sp,fp等寄存器的值保存和恢复.

因此,此处在goto reschedulePoint 时,其ip仍是指向RunCustomCodeMethod所对应的字节码,即runClinit.sp仍是指向初始化class所对应的栈,fp认识指向class所对应的frame…

因此,在那个Interpret的那个大switch中,仍会跳到对CUSTOMCODE的处理,此时的栈的情况如图:

在这里插入图片描述

因此在runClinit方法中,由于此时的状态为4,因此会执行如下代码:

  case 4: {
        /* Step 8:
         * Run the <clinit> method, if the class has one
         * 执行C的类或接口初始化方法。
         * 
         */
        METHOD thisMethod = getSpecialMethod(thisClass, clinitNameAndType);
        if (thisMethod) {// 对于class来说,是不存在<clinit>方法的

#if INCLUDEDEBUGCODE
            if (traceclassloading || traceclassloadingverbose) {
                START_TEMPORARY_ROOTS
                    fprintf(stdout, "Initializing class: '%s'\n",
                            getClassName((CLASS)thisClass));
                END_TEMPORARY_ROOTS
            }
#endif /* INCLUDEDEBUGCODE */

            topStack = 5;
            pushFrame(thisMethod);
            return;
        } else {
            /* No <clinit> method. */
            /* FALL THROUGH */
        }
    }
    case 5:
        /* Step 9:
         * Grab the monitor so we can change the flags, and wake up any
         * other thread waiting on us.
         * 如果正常地执行了类或接口的初始化方法,之后就请求获取LC,标记C的Class对象已经被完全初始化,通知所有正在等待的线程,接着释放LC,正常地退出整个过程。
         * SHORTCUT: 99% of the time, there is no contention for the class.
         * Since our scheduler is non-preemptive, if there is no contention
         * for this class, we just go ahead and unmark the class, without
         * bothering with the monitor.
         */

        if (!OBJECT_HAS_MONITOR(&thisClass->clazz)) {
            goto unmarkClass; // 此处最终执行这里
        }

        if (monitorEnter((OBJECT)thisClass) != MonitorStatusOwn) {
            /* When we wake up, we'll have the monitor */
            topStack = 6;
            return;
        } else {
            /* FALL THROUGH */
        }

在当前情况,最终会执行unmarkClass处的代码.

unmarkClass:
        setClassInitialThread(thisClass, NULL);
        setClassStatus(thisClass, CLASS_READY);
#if ENABLE_JAVA_DEBUGGER
        if (vmDebugReady) {
            CEModPtr cep = GetCEModifier();
            cep->loc.classID = GET_CLASS_DEBUGGERID(&thisClass->clazz);
            cep->threadID = getObjectID((OBJECT)CurrentThread->javaThread);
            cep->eventKind = JDWP_EventKind_CLASS_PREPARE;
            insertDebugEvent(cep);
        }
#endif /* ENABLE_JAVA_DEBUGGER */
        if (haveMonitor) {
            char *junk;
            monitorNotify((OBJECT)thisClass, TRUE); /* wakeup everyone */
            monitorExit((OBJECT)thisClass, &junk);
        }
        popFrame();
        return;

此处最终将class的状态设置为CLASS_READY,然后执行弹栈操作. popFrame的代码如下:

void popFrame()
{
    /* Unallocate a stack frame and restore virtual */ 
    /* machine registers to continue the execution */
    /* of the previous method. */

#if INCLUDEDEBUGCODE
        if (tracemethodcalls || tracemethodcallsverbose) {
            frameTracing(getFP()->thisMethod, "<=", 0);
        }
#endif /* INCLUDEDEBUGCODE */

    /* See frame.h */
    POPFRAMEMACRO

#if INCLUDEDEBUGCODE
    if (traceframes) {
        fprintf(stdout,
            "Popped a stack frame (thread: %lx, fp: %lx, sp: %lx, depth: %ld, stack: %lx)\n",
            (long)CurrentThread, (long)getFP(), 
            (long)getSP(), (long)frameDepth(), (long)getFP()->stack);
    }
#endif /* INCLUDEDEBUGCODE */

#if ENABLE_JAVA_DEBUGGER
    if (vmDebugReady)
        setEvent_FramePop();
#endif
}

这里最重要的是POPFRAMEMACRO,其宏展开如下:

#define POPFRAMEMACRO                                                          \
	setSP(getFP()->previousSp);    /* Restore previous stack pointer */        \
	setIP(getFP()->previousIp);    /* Restore previous instruction pointer */  \
	setFP(getFP()->previousFp);    /* Restore previous frame pointer */        \
	setLP(FRAMELOCALS(getFP()));   /* Restore previous locals pointer */       \
	setCP(getFP()->thisMethod->ofClass->constPool);

因此此时的情况如下:

在这里插入图片描述

其最终会执行对thread类的初始化.

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐