基于KK的代码。本文主要是介绍dalvik GC的一些概况。
DalvikGC概要
Davik GC的主要对象
- GCHeap : 管理GC内存的对象,全局只有一个,负责GC的分配和回收
- HeapSource: 管理Heap的对象。在GC初始化时,分配一段连续的地址空间(maximumSize), 负责管理Heap的增长
- Heap: 为分配准备的连续空间。HeapSource包含两个Heap
- HeapBitmap: 对Heap做mark映射的bitmap对象。HeapSource包含两个bitmap:liveBits和markBits
- GcMarkStack: 在Mark阶段使用到的堆栈
Dalvik GC的堆分布情况
GC的堆的创建
</pre><pre name="code" class="cpp">GcHeap* dvmHeapSourceStartup(size_t startSize, size_t maximumSize,
size_t growthLimit)
{
....
/*
* Allocate a contiguous region of virtual memory to subdivided
* among the heaps managed by the garbage collector.
*/
length = ALIGN_UP_TO_PAGE_SIZE(maximumSize); //获取的是堆的最大值
base = dvmAllocRegion(length, PROT_NONE, gDvm.zygote ? "dalvik-zygote" : "dalvik-heap");
//创建了一个连续地址空间。但是并没有实际的内存分配出来
if (base == NULL) {
return NULL;
}
/* Create an unlocked dlmalloc mspace to use as
* a heap source.
*/
//这个创建分配器
msp = createMspace(base, kInitialMorecoreStart, startSize);
if (msp == NULL) {
goto fail;
}
.....
//初始化heap
if (!addInitialHeap(hs, msp, growthLimit)) {
LOGE_HEAP("Can't add initial heap");
goto fail;
}
....
}
//addInitalHeap
static bool addInitialHeap(HeapSource *hs, mspace msp, size_t maximumSize)
{
assert(hs != NULL);
assert(msp != NULL);
if (hs->numHeaps != 0) {
return false;
}
hs->heaps[0].msp = msp;
hs->heaps[0].maximumSize = maximumSize;
hs->heaps[0].concurrentStartBytes = SIZE_MAX;
hs->heaps[0].base = hs->heapBase;
hs->heaps[0].limit = hs->heapBase + maximumSize;
hs->heaps[0].brk = hs->heapBase + kInitialMorecoreStart; //这是初次大小,以后按照这个递增
hs->numHeaps = 1;
return true;
}
这个函数只会在zygote中调用。dvm为zygote保留了一段连续地址空间,但是并没有分配内存,当需要的时候,使用dvmHeapSourceAllocAndGrow来增长。
App的内存堆布局
HeapSource是dalvik中管理堆对象的对象。HeapSource定义了成员Heap heaps[HEAP_SOURCE_MAX_HEAP_COUNT];
HEAP_SOURCE_MAX_HEAP_COUNT = 2。 heaps[0]永远是当前活动Heap, 即承担新对象分配的heap,而heap[1]则是zygote分配的对象。
事情是这样的,当zygote要fork一个新进程时,ZygoteInit.preFrok会在fork之前调用,该函数最终会调用到dalvik的dvmHeapSourceStartupBeforeFork, 然后调用addNewHeap。
addNewHeap会在原heap[0]的剩余空间创建一个新的heap,然后将heap[0] copy 到heap[1],新的heap copy到heap[0]。 然后再fork。
当然,这个过程只会在第一次fork时执行,此后就不会被执行了。
我们知道,zygote进程在启动后,会preload大量的classes, 资源,这些都会存放在zygote的space中,当zygote fork出新进程后,新进程就继承了这些资源。新进程的对象,则在新创建的heap上。
Dalvik GC 的Alloctor
dvm的GC Allocator是使用外部的dl malloc的 mspace来管理的。这个在libc中定义和实现。
在 dvm dvmHeapSourceStartup函数中调用create_mspace_with_base,在heap上传教对应的对象。
mspace的函数有
- create_mspace_with_base
- mspace_malloc/mspace_calloc
- mspace_bulk_free
- mspace_inspect_all
Dalvik GC过程
Dalvik GC的启动方式及入口
- GC_FOR_MALLOC: 当分配空间时,发现空间不足
- GC_CONCURRENT: 以并发方式启动GC
- GC_EXPLICIT: 通过system.runtime.gc调用的GC
- GC_BEFORE_OOM:OOM之前的GC
每种GC的处理方式又不同,根据它们的配置GcSpec数据的定义,可以用下表俩表示
并行清理软引用
FOR_MALLOC T F T
CONCURRENT T T T
EXPLICIT F T T
OOM F FF
说明 app heap被回收
zygote heap不回收 执行并行F时执行软引用
GC类型 | APP Heap不足 | 并行 | 清理软引用 |
FOR_MALLOC | 是 | 否 | 否 |
CONCURRENT | 是 | 是 | 否 |
EXPLICIT | 否 | 是 | 否 |
OOM | 否 | 否 | 否 |
- AppHeap不足:描述了GC 引起的原因里面是否包含了App Heap不足,即便不是App Heap引起的,也会清理App Heap;
- 并行:是否以并行方式GC
- 清理引用:是否清理引用。
Dalvik GC使用的算法
DVM 使用mark-sweep算法。
HeapSource 定义了HeapBitmap liveBits和markBits来标记bitmap。
make sweep算法简介
简单的mark sweep算法 (2色法)
- 新分配对象都是白色 (liveBits中做标记)
- 从root对象开始,遍历每个root对象,对每个对象做mark,使之变成黑色(markBits中做标记),同时将对象加入worklist中
- 遍历worklist中的对象的白色子对象,对每个白色子对象做mark,使之变成黑色,同时将该子对象加入worklist。
- worklist空后,就结束了mark过程
- 将所有白色对象清除(liveBits中有标记但是markBits中没有标记的对象)
- 黑色变为白色(用markBits替换liveBits)
增量mark-sweep算法 (3色法)
- mark roots时,暂停所有java线程,完成roots mark后,重启所有java线程
- 并行进行mark栈中对象
- 当java线程修改对象后,将被修改对象重新标记为gray (write barrier)
- 栈空后,再次暂停所有java线程, 重新mark gray对象
- 重启java线程对象
- sweep
dalvik中的 mark-sweep算法实现
dvmHeapMarkRootSet
- 由root classloader定义的系统class
- 对于每个线程:
- interpreted stack, from top to "curFrame"
- davik registers (args + local vars)
- JNI local references
- Automatic VM local reference (TrackedAlloc)
- Associated Thread/VMThread object
- ThreadGroups (could track & start with these instead of working upward from threads)
- JNI global references
- 内部string table
- gDvm.outOfMemoryObj
- Objects in debugger object registry
- interpreted stack, from top to "curFrame"
dvmHeapScanMarkedObjects
dvmHeapBitmapScanWalk(ctx->bitmap, scanBitmapCallback, ctx);
ctx是类型GcMarkContext, ctx->bitmap是heapsource的markBits,这是保存的都是root对象。
这种方法省去了worklist。
dvmHeapProcessReferences
dvmHeapSweepUnmarkedObjects
dvm 的并行GC
cardtable
CardTable中每一个小格代表一个card(1字节)。如果被设置,表示对应的card内的对象被修改,需要重新mark
在iput-object这样的指令插入对card table的修改
- dvmCardFromAddr: 从addr得到card的标志字节
- dvmMarkCard: 将card置脏
write barrier
dvmSetFieldObject: write barrier,调用dvmMarkCard,将card table置脏。这个函数在dvm内部和jni函数SetFieldObject中会被调用。
在虚拟机内部,则通过IPUT_OBJECT指令的汇编函数来实现:
.LOP_IPUT_OBJECT_finish:
....
ldr r2, [rSELF, #offThread_cardTable] @ r2<- card table base
...
cmp r0, #0 @ stored a null reference?
strneb r2, [r2, r9, lsr #GC_CARD_SHIFT] @ mark card if not
//r2地址最低字节正好是CARD_DIRTY值。
为了节省cpu,dvm故意将cardtable数组的前0x7f大小的位置空出,从0x7f后开始计算,于是,他就可以用cartable数组的地址的低8位(r2)来保存标志值了。