您的位置:首页 > 其它

DVM GC源码研究

2016-07-08 18:58 495 查看
基于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的堆的创建

在GC初始化时,HeapSource会创建一段连续空间,作为堆来使用。
这是由dvmHeapSourceStartup完成的。
部分代码如下:
</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的内存堆布局

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的启动方式及入口

dvmCollectGarbageInternal 函数是所有类型GC的入口。

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色法)



与2色法不同在于,当一个对象被mark后,不是变为黑色,而是变为灰色,这是该对象还在worklist中;当从worklist中取出该对象,并mark了它的所有子对象后,它才会变成黑色。

灰色表示对象本身已经被mark,但是其子对象没有被mark。

这种方法用于并行mark。方法是

mark roots时,暂停所有java线程,完成roots mark后,重启所有java线程
并行进行mark栈中对象
当java线程修改对象后,将被修改对象重新标记为gray (write barrier)
栈空后,再次暂停所有java线程, 重新mark gray对象
重启java线程对象
sweep

dalvik中的 mark-sweep算法实现

dvmHeapMarkRootSet

mark root对象,root对象包括:

由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

dvmHeapScanMarkedObjects

该函数首先调用
dvmHeapBitmapScanWalk(ctx->bitmap, scanBitmapCallback, ctx);
ctx是类型GcMarkContext, ctx->bitmap是heapsource的markBits,这是保存的都是root对象。

这种方法省去了worklist。

scanBitmapCallback函数扫描每个对象的子对象,然后将他们mark后加入到堆栈中。

然后调用 processMarkStack。该函数访问堆栈中的每个对象的子对象,然后再将子对象放入堆栈中。知道堆栈为空,就完成了扫描。
堆栈是用GcMarkStack对象实现的,该类用一个object* 数组来作为栈。

实现mark的是函数 markObjectNonNull。

dvmHeapProcessReferences

该函数主要负责处理各种引用,包括softRefereces, weakReferences, phatomReferences,
并且会调用finalize函数。

dvmHeapSweepUnmarkedObjects

该函数负责清除垃圾对象。最终负责释放对象的是函数dvmHeapSourceFreeList,也是用mspace来实现的。有兴趣的可以自己看看。

dvm 的并行GC

请考虑一个问题,如果当前正在 dvmHeapScanMarkedObjects,
 其中某个对象(加入是A对象)已经变成黑色了,这时某个java线程修改了A的某个field,改变了它的reachable树。这个时候,我们需要知道这个A对象已经被修改了,当dvmHeapScanMarkedObjects完成后,我们需要再次对这些对象进行Remark。
那么,我们怎么知道那些对象被修改了呢?

需要两个东西:CardTable和write barrier。

cardtable

如下图:



内存被分为若干card (如512字节为一个card)

CardTable中每一个小格代表一个card(1字节)。如果被设置,表示对应的card内的对象被修改,需要重新mark

在iput-object这样的指令插入对card table的修改

DVM中dvmCardTableStartup 初始化 card table。 每个card大小为GC_CARD_SIZE
(1<<7)。catd table 为每个card映射一个byte,该byte值是GC_CARD_CLEAN或者GC_CARD_DIRTY

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)来保存标志值了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: