本文来详细介绍kvm中的inlineCache.
其定义如下:
struct icacheStruct {
cell* contents; /* 指向实际要执行方法 */
BYTE* codeLoc; /* 指向引用内联缓存项的代码位置*/
short origParam; /* 原始字节码的参数,其值= codeLoc+1 */
BYTE origInst; /* 原始字节码 */
};
初始化
inlineCache的初始化是在InitializeInlineCaching中实现的,其代码如下:
void
InitializeInlineCaching(void)
{
/* Align the cache area so that accessing static variables is safe
* regardless of the alignment settings of the compiler
*/
// 1. 在持久代中分配InlineCache, INLINECACHESIZE = 128.因此,会分配长度为128的数组
InlineCache =
(ICACHE)callocPermanentObject(SIZEOF_ICACHE*INLINECACHESIZE+1);
InlineCachePointer = 0; // inline cache的指针
InlineCacheAreaFull = FALSE; // 用来指示inline cache是否已满
// 2. 清零
memset(InlineCache, 0, (SIZEOF_ICACHE*INLINECACHESIZE+1)*sizeof(CELL));
}
关于这点,有以下说明:
- icache是用于存储单个内联缓存项的结构。整个内联缓存只是一个icache数组。
- 一旦内联缓存区域满了,我们就开始重新使用从icache区域开始的最旧条目。引用重新使用的icache条目的方法的代码将替换为原始(预内联缓存)代码。换句话说,整个内联缓存过程是完全可逆和可重复的。
- 为了避免垃圾收集问题,我们不在内联缓存中存储任何动态堆指针!这确保了我们可以在垃圾收集期间忽略整个内联缓存区域
如何使用
kvm在解释执行的过程中,对INVOKEVIRTUAL,INVOKEINTERFACE,ANEWARRAY的处理过程中,使用到了inlineCache.
INVOKEVIRTUAL
在对INVOKEVIRTUAL的处理的过程中,有如下代码:
if (thisMethod) {
// 缓存处理
#if ENABLEFASTBYTECODES
if ( (cpMethod->accessFlags & (ACC_PRIVATE | ACC_FINAL))
|| (cpMethod->ofClass->clazz.accessFlags & ACC_FINAL)
) {
// 如果该方法是private 或者是final 或者该方法所属的类是final的,则替换字节码为INVOKESPECIAL_FAST
REPLACE_BYTECODE(ip, INVOKESPECIAL_FAST)
} else {
// 其他情况,则创建InlineCacheEntry,然后将自己码替换为INVOKEVIRTUAL_FAST,同时将操作数设为InlineCacheEntry的下标(iCacheIndex)
int iCacheIndex;
/* Replace the current bytecode sequence */
// iCacheIndex = createInlineCacheEntry((cell*)cellp, ip);
CREATE_CACHE_ENTRY((cell*)thisMethod, ip)
/**
* 宏展开为: REPLACE_BYTECODE(ip, bytecode) *ip = bytecode;
*/
REPLACE_BYTECODE(ip, INVOKEVIRTUAL_FAST)
putShort(ip + 1, iCacheIndex);
}
#endif /* ENABLEFASTBYTECODES */
TRACE_METHOD_ENTRY(thisMethod, "virtual");
// 调用方法
CALL_VIRTUAL_METHOD
}
此处CREATE_CACHE_ENTRY宏展开为createInlineCacheEntry,该方法定义在j2me_cldc/kvm/VmCommon/src/cache.c中.代码如下:
int
createInlineCacheEntry(cell* contents, BYTE* originalCode)
{
ICACHE thisICache;
int index;
/* Check first if inline cache is already full 1. 如果InlineCache已满,则进行释放,从头开始释放 */
if (InlineCacheAreaFull) {
releaseInlineCacheEntry(InlineCachePointer);
}
/* Allocate new entry / reallocate old one 2. 使用新的entry或者重新使用旧的*/
thisICache = &InlineCache[InlineCachePointer];
index = InlineCachePointer++;
/* Check whether the icache area is full now 3. 检查 INLINECACHE是否已满*/
if (InlineCachePointer == INLINECACHESIZE) {
InlineCacheAreaFull = TRUE;
InlineCachePointer = 0;
}
/* Initialize icache values 4. 初始化值*/
thisICache->contents = contents; // 此处指向的是方法
thisICache->codeLoc = originalCode; // 保存原有字节码的位置
thisICache->origInst = *originalCode;// 保存原有字节码的操作码
thisICache->origParam = getShort(originalCode+1);// 保存原有字节码的操作数
return index;
}
其中我们来看下releaseInlineCacheEntry.其代码如下:
static void
releaseInlineCacheEntry(int index)
{
// 1. 获得缓存
ICACHE thisICache = &InlineCache[index];
/* Read the pointer to the code location */
/* referring to this inline cache entry
* 2. 获得该缓存的字节码原先的位置 */
BYTE* codeLoc = (BYTE*)thisICache->codeLoc;
/* Restore original bytecodes */
#if ENABLE_JAVA_DEBUGGER
if (*codeLoc == BREAKPOINT) {
replaceEventOpcode(thisICache->origInst);
} else {
#endif
// 3. 还原原先的字节码
*codeLoc = thisICache->origInst;
#if ENABLE_JAVA_DEBUGGER
}
#endif
// 3. 还原原先的操作数
putShort(codeLoc+1, thisICache->origParam);
}
其中可以得出以下结论:
- InlineCache是循环使用的,如果InlineCache已满,则会覆盖最旧的cache.
- InlineCache是定长的,其元素个数为128。
此后,如果再次执行该方法,则会执行对INVOKEVIRTUAL_FAST的处理,其代码如下:
#if FASTBYTECODES
SELECT(INVOKEVIRTUAL_FAST)
/* Invoke instance method; dispatch based on dynamic class */
/* (fast version) */
/* Get the inline cache index */
unsigned int iCacheIndex;
ICACHE thisICache;
INSTANCE_CLASS defaultClass;
int argCount;
CLASS dynamicClass;
/* Get the inline cache index 1. 获得cache的下标 */
iCacheIndex = getUShort(ip + 1);
/* Get the inline cache entry 2. 获得对应的cache entry */
thisICache = GETINLINECACHE(iCacheIndex);
/* Get the default method stored in cache 3.获得该cache所对应的方法 */
thisMethod = (METHOD)thisICache->contents;
/* Get the class of the default method 4. 获得该方法所对应的class*/
defaultClass = thisMethod->ofClass;
/* Get the object pointer ('this') from the operand stack */
/* (located below the method arguments in the stack) 5. 获得该方法所对应的参数,thisObject*/
argCount = thisMethod->argCount;
thisObject = *(OBJECT*)(sp-argCount+1);
CHECK_NOT_NULL(thisObject);
/* This may be different than the default class 6. 获得thisObject所对应的class,注意此时dynamicClass可能会和缓存的class不一致*/
dynamicClass = thisObject->ofClass;
/* If the default class and dynamic class are the same, we can
* just execute the method. Otherwise a new lookup
*/
// 7.
if (dynamicClass != (CLASS)defaultClass) {// 7.1 如果dynamicClass和缓存的class不一致的话,则进行修正
/* Get method table entry based on dynamic class */
VMSAVE
// 7.1.1 找到对应的方法
thisMethod = lookupDynamicMethod(dynamicClass, thisMethod);
VMRESTORE
/* Update inline cache entry with the newly found method 7.1.2 修改对应的方法 */
thisICache->contents = (cell*)thisMethod;
IncrInlineCacheMissCounter(); // 此处为宏,#define IncrInlineCacheMissCounter() /**/
}
else IncrInlineCacheHitCounter(); // 此处为宏,#define IncrInlineCacheHitCounter() /**/
if (!thisMethod) {
fatalIcacheMethodError(thisICache);
} else {
TRACE_METHOD_ENTRY(thisMethod, "fast virtual");
CALL_VIRTUAL_METHOD // 调用方法
}
DONEX
#endif
INVOKEINTERFACE
在对INVOKEINTERFACE的处理的过程中,有如下代码:
#if INFREQUENTSTANDARDBYTECODES
SELECT(INVOKEINTERFACE) /* Invoke interface method */
unsigned int cpIndex;
unsigned int argCount;
/* 1.获得方法在常量池中的index */
cpIndex = getUShort(ip + 1);
/* 2.获得参数的数量*/
argCount = ip[3];
/* Resolve constant pool reference */
VMSAVE
// 3. 得到对应的方法
thisMethod = resolveMethodReference(cp_global, cpIndex, FALSE,
fp_global->thisMethod->ofClass);
VMRESTORE
if (thisMethod) {
INSTANCE_CLASS dynamicClass;
/* Get "this" */
thisObject = *(OBJECT*)(sp-argCount+1);
CHECK_NOT_NULL(thisObject);
dynamicClass = ((INSTANCE)thisObject)->ofClass;
VMSAVE
// 4. 在当前类中找到对应的方法
thisMethod = lookupMethod((CLASS)dynamicClass,
thisMethod->nameTypeKey,
fp_global->thisMethod->ofClass);
VMRESTORE
if (thisMethod != NULL &&
(thisMethod->accessFlags & (ACC_PUBLIC | ACC_STATIC)) == ACC_PUBLIC) {
#if ENABLEFASTBYTECODES
/*
* 5. 加入到缓存中 */
int iCacheIndex;
CREATE_CACHE_ENTRY((cell*)thisMethod, ip)
REPLACE_BYTECODE(ip, INVOKEINTERFACE_FAST)
putShort(ip + 1, iCacheIndex);
#endif /* ENABLEFASTBYTECODES */
TRACE_METHOD_ENTRY(thisMethod, "interface");
CALL_INTERFACE_METHOD
}
}
VMSAVE
fatalSlotError(cp, cpIndex);
VMRESTORE
DONE(0)
#endif
此后,如果再次执行该方法,则会执行对INVOKEINTERFACE_FAST的处理,其代码如下:
#if FASTBYTECODES
SELECT(INVOKEINTERFACE_FAST)
/* Invoke interface method (fast version) */
/* Get the inline cache index */
unsigned int iCacheIndex;
unsigned int argCount;
ICACHE thisICache;
INSTANCE_CLASS defaultClass;
CLASS dynamicClass;
/* Get the inline cache index 1. 获得在cache中的index*/
iCacheIndex = getUShort(ip + 1);
/* Get the argument count (specific to INVOKEINTERFACE bytecode) 2. 获得参数的数量 */
argCount = ip[3];
/* Get the inline cache entry 3. 获得对应的cache */
thisICache = GETINLINECACHE(iCacheIndex);
/* Get the default method stored in cache 4. 获得对应的方法 */
thisMethod = (METHOD)thisICache->contents;
/* Get the class of the default method 5. 获得缓存中的class */
defaultClass = thisMethod->ofClass;
/* Get the object pointer ('this') from the operand stack 6. 获得对应的this*/
thisObject = *(OBJECT*)(sp-argCount+1);
CHECK_NOT_NULL(thisObject);
/* Get the runtime (dynamic) class of the object */
dynamicClass = thisObject->ofClass;
/* If the default class and dynamic class are the same, DONE(1) 7. 如果this和缓存中的class,则进行修正*/
if (dynamicClass != (CLASS)defaultClass) {
/* Get method table entry based on dynamic class */
VMSAVE
// 7.1 重新查找方法
thisMethod = lookupMethod(dynamicClass, thisMethod->nameTypeKey,
fp_global->thisMethod->ofClass);
VMRESTORE
/* Update inline cache entry with the newly found method 7.2 修改缓存 */
thisICache->contents = (cell*)thisMethod;
IncrInlineCacheMissCounter();
} else {
IncrInlineCacheHitCounter();
}
if (thisMethod == NULL ||
((thisMethod->accessFlags & (ACC_PUBLIC | ACC_STATIC)) != ACC_PUBLIC)) {
fatalIcacheMethodError(thisICache);
} else {
TRACE_METHOD_ENTRY(thisMethod, "fast interface");
CALL_INTERFACE_METHOD; // 8. 调用方法
}
DONEX
#endif
此处和对INVOKEVIRTUAL_FAST的处理一致,不做过多解释.
ANEWARRAY
在对ANEWARRAY的处理的过程中,有如下代码:
SELECT(ANEWARRAY) /* Create new array of reference type */
/* Get the CONSTANT_Class index 1. 获得CONSTANT_Class*/
unsigned int cpIndex = getUShort(ip + 1);
long arrayLength = topStack;
CLASS elemClass;
ARRAY_CLASS thisClass;
ARRAY result;
/* Get the corresponding class pointer */
VMSAVE
// 2. 获得数组元素对应的class
elemClass =
resolveClassReference(cp_global, cpIndex, fp_global->thisMethod->ofClass);
thisClass = getObjectArrayClass(elemClass);
VMRESTORE
#if ENABLEFASTBYTECODES
{ // 3. 加入缓存中
int iCacheIndex;
/* Note that instantiateArray may change the ip on an error */
CREATE_CACHE_ENTRY((cell*)thisClass, ip)
REPLACE_BYTECODE(ip, ANEWARRAY_FAST)
putShort(ip + 1, iCacheIndex);
}
#endif /* ENABLEFASTBYTECODES */
VMSAVE
// 4. 初始化数组
result = instantiateArray(thisClass, arrayLength);
VMRESTORE
if (result != NULL) {
topStackAsType(ARRAY) = result;
ip += 3;
}
DONE_R
#endif
此处的功能是将ANEWARRAY修改为ANEWARRAY_FAST,同时将原来指向常量池中的index改为指向缓存的下标.其目的是减少resolveClassReference的操作.
则此后,再初始化该数组,则执行ANEWARRAY_FAST,其处理代码如下:
#if FASTBYTECODES
SELECT(ANEWARRAY_FAST)
/* Create new array of reference type (fast version) */
ARRAY result;
/* 1. 获得cache的下标 */
unsigned int iCacheIndex = getUShort(ip + 1);
/* 2. 获得对应的cache */
ICACHE thisICache = GETINLINECACHE(iCacheIndex);
/* 3. 获得对应的类 */
ARRAY_CLASS thisClass = (ARRAY_CLASS)thisICache->contents;
long arrayLength = topStack;
VMSAVE
// 4. 初始化数组
result = instantiateArray(thisClass, arrayLength);
VMRESTORE
if (result != NULL) {
topStackAsType(ARRAY) = result;
ip += 3;
}
DONE(0)
#endif
cache的释放
InlineCache的释放是在KVM_Cleanup方法中,调用FinalizeInlineCaching实现的,其代码如下:
void
FinalizeInlineCaching(void)
{
// 1. 获得最后一个下标
int last = InlineCacheAreaFull ? INLINECACHESIZE : InlineCachePointer;
// 2. 通过循环,依次释放之
while (--last >= 0) {
releaseInlineCacheEntry(last);
}
// 3.修改指示器
InlineCachePointer = 0;
InlineCacheAreaFull = FALSE;
}
以上步骤简单,同时releaseInlineCacheEntry在上文中有介绍.
总结
通过以上的分析,可以得到如下结论:
InlineCache的目的是
- 减少在方法调用时,查找最终要调用的方法的开销
- 减少在初始化对象数组时,对数组元素类型解析的开销