kvm在启动时的步骤如下:
- 创建ROM镜像,此处为宏,定义在j2me_cldc/kvm/VmCommon/h/garbage.h
- 初始化FPU
- 初始化异步I/0系统
- 初始化本地代码
- 初始化vm,此处为宏,定义在j2me_cldc/kvm/VmUnix/h/machine_md.h
- 初始化全局变量
- 初始化性能统计的变量,定义在 j2me_cldc/kvm/VmCommon/h/profiling.h
- 初始化内存系统
- 初始化内部hash 表 --> 本文介绍该步骤
- 初始化inline cache --> 本文介绍该步骤
- 初始化类加载接口
- 初始化VM所需要的内部类
- 初始化class文件验证器
- 初始化事件处理系统
- 加载主类
- 解析参数
- 初始化多线程
- 初始化系统类
- 解释执行
初始化HASH表
此处的代码如下:
void InitializeHashtables() {
if (!ROMIZING) {
createHashTable(&UTFStringTable, UTF_TABLE_SIZE); // 256
createHashTable(&InternStringTable, INTERN_TABLE_SIZE); // 32
createHashTable(&ClassTable, CLASS_TABLE_SIZE); // 32
}
}
可以知道,在kvm启动过程中,会创建3个hash表: UTFStringTable, InternStringTable, ClassTable.
这里创建过程中调用了createHashTable。其代码如下:
void
createHashTable(HASHTABLE *tablePtr, int bucketCount) {
// 1. 计算分配大小
int objectSize = SIZEOF_HASHTABLE(bucketCount);
// 2. 进行分配
HASHTABLE table = (HASHTABLE)callocPermanentObject(objectSize);
// 3. 赋值
table->bucketCount = bucketCount;
*tablePtr = table;
}
其中第一步如下:
SIZEOF_HASHTABLE(bucketCount) = (StructSizeInCells(HashTable) + (n - 1)) = ((sizeof(struct HashTable) + 3) >> 2) + (n-1) // 注意,此处计算的是要分配多少个字节
HashTable的定义如下:
typedef struct HashTable {
long bucketCount; /* bucket的数量*/
long count; /* 在table中有多少个元素 */
cell *bucket[1]; /* bucket数组*/
} *HASHTABLE;
第二步,在持久代中分配对象,其代码如下:
cell*
callocPermanentObject(long size) {
cell* result;
#if ENABLE_HEAP_COMPACTION
result = PermanentSpaceFreePtr - size;
PermanentSpaceFreePtr = result;
if (result < CurrentHeapEnd) {
/* We have to expand permanent memory 此时需要扩展持久代*/
cell* newPermanentSpace = CurrentHeapEnd;
do {
newPermanentSpace = PTR_OFFSET(newPermanentSpace, -0x800); // 递减2Kbytes
} while (newPermanentSpace > result);
/* We need to expand permanent memory to include the memory between
* newPermanentSpace and CurrentHeapEnd
*/
/* We pass GC a request that is larger than it could possibly
* fulfill, so as to force a GC.
* 进行full gc
*/
garbageCollect(AllHeapEnd - AllHeapStart);
if (newPermanentSpace < (cell*)FirstFreeChunk + 2 * HEADERSIZE) {// 如果内存不足,则抛出异常
raiseExceptionWithMessage(OutOfMemoryError,
KVM_MSG_UNABLE_TO_EXPAND_PERMANENT_MEMORY);
} else {
int newFreeSize =
(newPermanentSpace - (cell*)FirstFreeChunk - HEADERSIZE);
memset(newPermanentSpace, 0,
PTR_DELTA(CurrentHeapEnd, newPermanentSpace));
CurrentHeapEnd = newPermanentSpace;
// 由于进行压缩后,java heap 中只有一块可使用空间,而这块空间大小就是newFreeSize
FirstFreeChunk->size = newFreeSize << TYPEBITS;
}
}
return result;
#else /* ENABLE_HEAP_COMPACTION */
result = callocObject(size, GCT_NOPOINTERS);
OBJECT_HEADER(result) |= STATICBIT;
return result;
#endif /* ENABLE_HEAP_COMPACTION */
}
同样,关于gc的问题,我们后续有文章介绍.对于当前情况,如图所示:
初始化inline cache
此步骤的代码如下:
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;
InlineCacheAreaFull = FALSE;
// 2. 清零
memset(InlineCache, 0, (SIZEOF_ICACHE*INLINECACHESIZE+1)*sizeof(CELL));
}
InlineCache的定义如下:
struct icacheStruct {
cell* contents; /* 指向实际要执行方法 */
BYTE* codeLoc; /* 指向引用内联缓存项的代码位置*/
short origParam; /* 原始字节码的参数,其值= codeLoc+1 */
BYTE origInst; /* 原始字节码 */
};
关于这点,有以下说明:
- icache是用于存储单个内联缓存项的结构。整个内联缓存只是一个icache数组。
- 一旦内联缓存区域满了,我们就开始重新使用从icache区域开始的最旧条目。引用重新使用的icache条目的方法的代码将替换为原始(预内联缓存)代码。换句话说,整个内联缓存过程是完全可逆和可重复的。
- 为了避免垃圾收集问题,我们不在内联缓存中存储任何动态堆指针!这确保了我们可以在垃圾收集期间忽略整个内联缓存区域