kvm解释器-004

kvm 同时被 2 个专栏收录
51 篇文章 1 订阅
50 篇文章 3 订阅

本文介绍主类main方法执行的过程.其字节码如下:

另外,此处主类反编译后的结果如下:

# 参数 hejiarui$ javap -v -l -p -c -s -constants KVMTest.class 
Classfile KVMTest.class
  Last modified Jul 8, 2019; size 415 bytes
  MD5 checksum be7ceda6878f53d188a60205926bab6f
  Compiled from "KVMTest.java"
public class KVMTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // success
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // KVMTest
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               KVMTest.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               success
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               KVMTest
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public KVMTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String success
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
}
SourceFile: "KVMTest.java"

getstatic

此处我们来看getstatic所对应的字节码.此处在kvm中的处理如下:

#if INFREQUENTSTANDARDBYTECODES
SELECT(GETSTATIC)                        /* Get static field from class */
        /* Get the CONSTANT_Fieldref index */
        unsigned int cpIndex;
        FIELD field;
        int currentToken = TOKEN; // (*ip)

        /* Get the constant pool index */
        cpIndex = getUShort(ip + 1); // 获得index

        /* Resolve constant pool reference */
        VMSAVE
	    // 1. 获得字段
        field = resolveFieldReference(cp_global, cpIndex, TRUE, currentToken,
                                      fp_global->thisMethod->ofClass);
        VMRESTORE

     
		// 2. 如果该字段所对应的类的状态为CLASS_ERROR则抛出异常
        if (field && field->ofClass->status == CLASS_ERROR) {
            VMSAVE
                raiseExceptionWithMessage(NoClassDefFoundError,
                    KVM_MSG_EXPECTED_INITIALIZED_CLASS);
            VMRESTORE
        }

        // 3. 如果该字段所对应的类没有初始化,则进行初始化
        if (field && !CLASS_INITIALIZED(field->ofClass)) {
            VMSAVE
            /* Push class initialization frame */
            initializeClass(field->ofClass);
            VMRESTORE
            goto reschedulePoint;
        }

        if (field) {
            void *location = field->u.staticAddress;

#if ENABLEFASTBYTECODES
                REPLACE_BYTECODE(ip, ((field->accessFlags & ACC_DOUBLE)
                         ? GETSTATIC2_FAST
                         : (field->accessFlags & ACC_POINTER)
                         ? GETSTATICP_FAST : GETSTATIC_FAST))
#endif

            /* Load either one or two cells depending on field type
             * 如果存在,则进行压栈,压栈时区分是否是ACC_DOUBLE */
            if (field->accessFlags & ACC_DOUBLE) {
                oneMore;
                COPY_LONG(sp, location);
                oneMore;
            } else {
                pushStack(*(cell *)location);
            }
        } else {
        	// 如果字段不存在,则抛出异常
            VMSAVE
            fatalSlotError(cp, cpIndex);
            VMRESTORE
        }
DONE(3) 
#endif

此处有几点说一下:

  1. 静态字段压栈时是将指向PrintStream的指针压入栈中.对应的代码为:

    pushStack(*(cell *)location);
    
  2. DONE(3) 是个宏,宏展开后,为

    } goto next3;
    

    则此时执行的代码为:

    #if !RESCHEDULEATBRANCH
     	next3:  ip++;
     	next2:  ip++;
     	next1:  ip++;
     	next0:
     	reschedulePoint:
     	    RESCHEDULE
     	#endif
     	
     	   /*
     	    * If RESCHEDULEATBRANCH is defined then we only test for thread
     	    * scheduling when reschedulePoint is called.
     	    */
     	
     	#if RESCHEDULEATBRANCH
     	reschedulePoint:
     	    RESCHEDULE
     	#if ENABLE_JAVA_DEBUGGER
     	    goto next0a;
     	#else
     	    goto next0;
     	#endif
    

    其最终的效果是ip增加了3,因为getstatic的长度为3.

此时栈中的情况如下:

在这里插入图片描述


关于这点做如下说明:

  1. 首先执行如下代码:

    switch (signature[0]) {
                    case 'D': case 'J':   accessFlags |= ACC_DOUBLE;   break;
                      case 'L': case '[':   accessFlags |= ACC_POINTER;  break;
     }
     
     if (isStatic) {
                 	// 如果是static的话,则读取其属性,设置其值的下标
                     loadStaticFieldAttributes(ClassFileH, CurrentClass,
                                               thisField, StringPoolH);
                     if (accessFlags & ACC_POINTER) {
                         staticPtrCount++;
                     } else {
                         staticNonPtrCount += (accessFlags & ACC_DOUBLE) ? 2 : 1;
                     }
                 } else {
                     ignoreAttributes(ClassFileH, StringPoolH);
    }
    

    由于是对象,因此该字段的accessFlags就添加了ACC_POINTER标记.同样,也就增加了staticPtrCount

  2. 接着,将该字段分配在持久代中,代码如下:

    void **nextPtrField = (void **)statics->data;
    ....
    
    cpIndex = (unsigned short)(thisField->u.offset);
                 if (thisField->accessFlags & ACC_POINTER) {
                     /* The only possible initialization is for a string 这种情况只能是string,这里是对string的特殊处理,对于其他对象,数组,都是在<cinit>方法中初始化的*/
                     thisField->u.staticAddress = nextPtrField;
                     if (cpIndex != 0) {
                         verifyConstantPoolEntry(CurrentClass, cpIndex,
                                                 CONSTANT_String);
                         *(INTERNED_STRING_INSTANCE *)nextPtrField =
                             CP_ENTRY(cpIndex).String;
                     }
                     nextPtrField++;
    }
    
  3. 而在对getstatic处理的过程中,其最终使用如下代码压栈:

    void *location = field->u.staticAddress;
    pushStack(*(cell *)location); // 此处是指向PrintStream的指针.
    

    因此,在栈中压入的是指向PrintStream的指针.

ldc

此处执行的字节码是

3: ldc           #3                  // String success

而在kvm中对应的代码为:

#if STANDARDBYTECODES
SELECT(LDC)       /* Push item from constant pool onto the operand stack */
        unsigned int cpIndex = ip[1];
        CONSTANTPOOL_ENTRY thisEntry = &cp->entries[cpIndex];
        pushStack(thisEntry->integer);
DONE(2)
#endif

此处存放的是指向success的字符串的指针.因此此处的情况如图所示:

在这里插入图片描述


关于以上为何是指针.这点解释一下.

对于success字符串而言.是在loadConstantPool方法中进行处理的.

  1. 由于其声明如下:

    #3 = String             #18            // success
    #18 = Utf8               success
    

    因此,在loadConstantPool中首先是对String处理,代码如下:

    switch (tag) {
             case CONSTANT_String:
             case CONSTANT_Class: {
                 /* A single 16-bit entry that points to a UTF string */
                 unsigned short nameIndex = loadShort(ClassFileH); // 此处获得的是18
                 RAW_POOL(cpIndex).integer = nameIndex;
                 break;
    }
    
    

    因此,此处在主类所对应的运行时常量池中下标为3处,存放的是18.

    然后接着处理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);
    }
    

    将success该字符串放入string_pool中。注意,这个string_pool是临时的

  2. 接着,执行如下代码:

     case CONSTANT_String: {
                 unsigned short nameIndex =
                     (unsigned short)RAW_POOL(cpIndex).integer;// 此处获得的是18
                 char *name = getUTF8String(&StringPool, nameIndex);// 此处获得success的指针
                 INTERNED_STRING_INSTANCE string =
                     internString(name, strlen(name));// 在持久代中分配
                 CP_ENTRY(cpIndex).String = string;// 保存指针
                 break;
    }
    

    因此,最终主类所对应的运行时常量池中下标为3处,存放的是指向success字符串的指针.

  3. 由于constantPoolEntryStruct是一个union,其定义如下:

    union constantPoolEntryStruct {
     struct { 
         unsigned short classIndex;
         unsigned short nameTypeIndex;
     }               method;  /* Also used by Fields */
     CLASS           clazz;
     INTERNED_STRING_INSTANCE String;
     cell           *cache;   /* Either clazz or String */
     cell            integer;
     long            length;
     NameTypeKey     nameTypeKey;
     NameKey         nameKey;
     UString         ustring;
     };
    

    因此,在ldc的处理中,使用thisEntry->integer和thisEntry–> String是一样的,都是4字节.

invokevirtual

此处执行的字节码为:

5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

而在kvm中对应的代码为:

#if INFREQUENTSTANDARDBYTECODES
SELECT(INVOKEVIRTUAL)
        /* Invoke instance method; dispatch based on dynamic class */

        /* Get the CONSTANT_Methodref index */
        unsigned int cpIndex;
        METHOD cpMethod;

        /* Get the constant pool index */
        cpIndex = getUShort(ip + 1);

        /* Resolve constant pool reference */
        VMSAVE
		// 1. 获得对应的方法
        cpMethod = resolveMethodReference(cp_global, cpIndex, FALSE,
                                          fp_global->thisMethod->ofClass);
        VMRESTORE
        if (cpMethod) {
            CLASS dynamicClass;

            /* Calculate the number of parameters  from signature */
            int argCount = cpMethod->argCount; // 获得参数数量

            /* Get this */
            thisObject = *(OBJECT*)(sp-argCount+1);// 获得this
            CHECK_NOT_NULL(thisObject);

            /* Get the dynamic class of the object */
            dynamicClass = thisObject->ofClass;// 获得当前对象所对应的class

            /* Find the actual method */
            VMSAVE
			// 找到对应的方法
            thisMethod = lookupDynamicMethod(dynamicClass, cpMethod);
            VMRESTORE

            /*
             * In order for INVOKEVIRTUAL to be called, an instance
             * of the object must exist. Thus, the class initialization
             * is not needed since the class in question will already be 
             * initialized.
             */
            if (thisMethod) {
               // 缓存处理
#if ENABLEFASTBYTECODES
                if (   (cpMethod->accessFlags & (ACC_PRIVATE | ACC_FINAL))
                    || (cpMethod->ofClass->clazz.accessFlags & ACC_FINAL)
                   ) {
                    REPLACE_BYTECODE(ip, INVOKESPECIAL_FAST)
                } else {
                    int iCacheIndex;
                    /* Replace the current bytecode sequence */
                    CREATE_CACHE_ENTRY((cell*)thisMethod, ip)
                    REPLACE_BYTECODE(ip, INVOKEVIRTUAL_FAST)
                    putShort(ip + 1, iCacheIndex);
                }
#endif /* ENABLEFASTBYTECODES */

                TRACE_METHOD_ENTRY(thisMethod, "virtual");

                // 调用方法
                CALL_VIRTUAL_METHOD
            } else {
                VMSAVE
                fatalSlotError(cp, cpIndex);
                VMRESTORE
            }
        } else {
            VMSAVE
            fatalSlotError(cp, cpIndex);
            VMRESTORE
        }
DONE(0)
#endif

而CALL_VIRTUAL_METHOD是一个宏,展开后为:

#define CALL_VIRTUAL_METHOD   {                 \
    goto callMethod_virtual;                    \
}

因此最终会执行如下代码:

 callMethod_virtual:
        callMethod_static:
        callMethod_special:
            invokerSize = 3;       /* Size of the bytecode */

        callMethod_general: {

            INC_CALLS

            /* 如果是本地方法,则调用本地方法*/
            if (thisMethod->accessFlags & ACC_NATIVE) {
                ip += invokerSize;
                VMSAVE
                invokeNativeFunction(thisMethod);
                VMRESTORE
                TRACE_METHOD_EXIT(thisMethod);
                goto reschedulePoint;
            }

            /* 如果该方法是抽象的,则抛出异常 */
            if (thisMethod->accessFlags & ACC_ABSTRACT) {
                VMSAVE
                raiseExceptionWithMessage(AbstractMethodError, methodName(thisMethod));
                VMRESTORE
            }


            // 压栈处理
            thisObjectGCSafe = thisObject;
            VMSAVE
            pushFrame(thisMethod);
            VMRESTORE

            /* Advance to the next instruction on return */
            fp->previousIp += invokerSize;

            /*  同步处理 */
            if (thisMethod->accessFlags & ACC_SYNCHRONIZED) {
                VMSAVE
                monitorEnter(thisObjectGCSafe);
                VMRESTORE
                fp->syncObject = thisObjectGCSafe;
            }

            thisObjectGCSafe = NULL;
            goto reschedulePoint;

因此,此处也就是建立println方法的栈帧,进行方法调用.此处就不展开了.

RETURN

此处我们看主类main方法中的最后一个字节码,return.在kvm中,处理如下:

#if STANDARDBYTECODES
SELECT6(IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN)
        /* Return from method */
        BYTE*  previousIp    = fp->previousIp;
        OBJECT synchronized  = fp->syncObject;

        TRACE_METHOD_EXIT(fp->thisMethod);

        if (synchronized != NULL) {
            char*  exitError;
            if (monitorExit(synchronized, &exitError) == MonitorStatusError) {
                exception = exitError;
                goto handleException;
            }
        }

        /* Special case where we are killing a thread */
        if (previousIp == KILLTHREAD) { // 此处执行这里
            VMSAVE
            stopThread(); // 停止线程
            VMRESTORE
            if (areAliveThreads()) {
                goto reschedulePoint;
            } else {
                return;                   /* Must be the end of the program */
            }
        }

        /* Regular case where we pop the stack frame and return data */
        if ((TOKEN & 1) == 0) {
            /* The even ones are all the return of a single value */
            cell data = topStack;
            POP_FRAME
            pushStack(data);
        } else if (TOKEN == RETURN) {
            POP_FRAME
        } else {
            /* We don't care whether it's little or big endian. . . */
            long t2 = sp[0];
            long t1 = sp[-1];
            POP_FRAME
            pushStack(t1);
            pushStack(t2);

        }
        goto reschedulePoint;
DONEX
#endif

由于此处的previousIp = KILLTHREAD,因此会调用stopThread.而关于该方法,我们下文介绍。(写不动了…)

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值