本文介绍主类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
此处有几点说一下:
-
静态字段压栈时是将指向PrintStream的指针压入栈中.对应的代码为:
pushStack(*(cell *)location);
-
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.
此时栈中的情况如下:
关于这点做如下说明:
-
首先执行如下代码:
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
-
接着,将该字段分配在持久代中,代码如下:
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++; }
-
而在对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方法中进行处理的.
-
由于其声明如下:
#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是临时的
-
接着,执行如下代码:
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字符串的指针.
-
由于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.而关于该方法,我们下文介绍。(写不动了…)