DVM GC源码研究
2016-07-08 18:58
495 查看
基于KK的代码。本文主要是介绍dalvik GC的一些概况。
HeapSource: 管理Heap的对象。在GC初始化时,分配一段连续的地址空间(maximumSize), 负责管理Heap的增长
Heap: 为分配准备的连续空间。HeapSource包含两个Heap
HeapBitmap: 对Heap做mark映射的bitmap对象。HeapSource包含两个bitmap:liveBits和markBits
GcMarkStack: 在Mark阶段使用到的堆栈
这是由dvmHeapSourceStartup完成的。
部分代码如下:
这个函数只会在zygote中调用。dvm为zygote保留了一段连续地址空间,但是并没有分配内存,当需要的时候,使用dvmHeapSourceAllocAndGrow来增长。
![](https://img-blog.csdn.net/20160708190032408?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
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上。
在 dvm dvmHeapSourceStartup函数中调用create_mspace_with_base,在heap上传教对应的对象。
mspace的函数有
create_mspace_with_base
mspace_malloc/mspace_calloc
mspace_bulk_free
mspace_inspect_all
这些函数的用法和简单,具体的实现可以在看对应源码。
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时执行软引用
AppHeap不足:描述了GC 引起的原因里面是否包含了App Heap不足,即便不是App Heap引起的,也会清理App Heap;
并行:是否以并行方式GC
清理引用:是否清理引用。
HeapSource 定义了HeapBitmap liveBits和markBits来标记bitmap。
![](https://img-blog.csdn.net/20160708190232612?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
这个过程是:
新分配对象都是白色 (liveBits中做标记)
从root对象开始,遍历每个root对象,对每个对象做mark,使之变成黑色(markBits中做标记),同时将对象加入worklist中
遍历worklist中的对象的白色子对象,对每个白色子对象做mark,使之变成黑色,同时将该子对象加入worklist。
worklist空后,就结束了mark过程
将所有白色对象清除(liveBits中有标记但是markBits中没有标记的对象)
黑色变为白色(用markBits替换liveBits)
![](https://img-blog.csdn.net/20160708190252097?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
与2色法不同在于,当一个对象被mark后,不是变为黑色,而是变为灰色,这是该对象还在worklist中;当从worklist中取出该对象,并mark了它的所有子对象后,它才会变成黑色。
灰色表示对象本身已经被mark,但是其子对象没有被mark。
这种方法用于并行mark。方法是
mark roots时,暂停所有java线程,完成roots mark后,重启所有java线程
并行进行mark栈中对象
当java线程修改对象后,将被修改对象重新标记为gray (write barrier)
栈空后,再次暂停所有java线程, 重新mark gray对象
重启java线程对象
sweep
由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
这种方法省去了worklist。
scanBitmapCallback函数扫描每个对象的子对象,然后将他们mark后加入到堆栈中。
然后调用 processMarkStack。该函数访问堆栈中的每个对象的子对象,然后再将子对象放入堆栈中。知道堆栈为空,就完成了扫描。
堆栈是用GcMarkStack对象实现的,该类用一个object* 数组来作为栈。
实现mark的是函数 markObjectNonNull。
并且会调用finalize函数。
其中某个对象(加入是A对象)已经变成黑色了,这时某个java线程修改了A的某个field,改变了它的reachable树。这个时候,我们需要知道这个A对象已经被修改了,当dvmHeapScanMarkedObjects完成后,我们需要再次对这些对象进行Remark。
那么,我们怎么知道那些对象被修改了呢?
需要两个东西:CardTable和write barrier。
![](https://img-blog.csdn.net/20160708190207565?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
内存被分为若干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置脏
在虚拟机内部,则通过IPUT_OBJECT指令的汇编函数来实现:
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 | 否 | 否 | 否 |
并行:是否以并行方式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)来保存标志值了。
相关文章推荐
- 高精度开方数
- js过滤emoji表情符号
- Zigzag
- 独立同分布
- git stash暂存当前正在进行的工作
- 查看R函数源代码
- Nginx因Selinux服务导致无法远程访问
- 美团2016 ,求最大值
- 关于OkHttp3的学习
- 高精度最小公倍数
- linux nginx本机可以访问html,远程无法访问的解决办法
- uva 106 Fermat vs. Pythagoras
- windows自动开关机
- 高精度 A+B II
- 爱维Linux - 51CTO技术博客 - 领先的IT技术博客
- 2016书单
- [2016/7/8]动态规划入门
- SpEL运算符
- 头文件包含顺序处理方法
- bzoj 3900: 交换茸角