1. HeapBitmap, markStack,CardTable的创建
Dalvik 虚拟机启动流程中的Heap启动流程如下:
std::string dvmStartup(int argc,..) { ... if (!dvmGcStartup()) { return "dvmGcStartup failed"; } ... } bool dvmGcStartup() { ... return dvmHeapStartup(); } bool dvmHeapStartup() { GcHeap *gcHeap; if (gDvm.heapGrowthLimit == 0) { gDvm.heapGrowthLimit = gDvm.heapMaximumSize; } gcHeap = dvmHeapSourceStartup(gDvm.heapStartingSize, gDvm.heapMaximumSize, gDvm.heapGrowthLimit); ... if (!dvmCardTableStartup(gDvm.heapMaximumSize, gDvm.heapGrowthLimit)) { LOGE_HEAP("card table startup failed."); return false; } }
在 dvmHeapStartup 中,我们已经可以看到 CardTable的影子;而Bitmap 和 markStack实际上是在 dvmHeapSourceStartup()之中,GcHeap创建完成之后进行创建的。看代码:
GcHeap* dvmHeapSourceStartup(size_t startSize, size_t maximumSize, size_t growthLimit) { ...//在此之前,GcHeap已经完成malloc if (!addInitialHeap(hs, msp, growthLimit)) { LOGE_HEAP("Can't add initial heap"); goto fail; } if (!dvmHeapBitmapInit(&hs->liveBits, base, length, "dalvik-bitmap-1")) { LOGE_HEAP("Can't create liveBits"); goto fail; } if (!dvmHeapBitmapInit(&hs->markBits, base, length, "dalvik-bitmap-2")) { LOGE_HEAP("Can't create markBits"); dvmHeapBitmapDelete(&hs->liveBits); goto fail; } if (!allocMarkStack(&gcHeap->markContext.stack, hs->maximumSize)) { ALOGE("Can't create markStack"); dvmHeapBitmapDelete(&hs->markBits); dvmHeapBitmapDelete(&hs->liveBits); goto fail; } ... }
至此,这三个数据结构的创建我们都已经看到;
其中,在Heap创建完成后,依次有两个Heapbitmap的创建,分别为"dalvik-bitmap-1" 与 "dalvik-bitmap-2";他们初始化的第一个参数分别为 liveBits 与 markBits;
再次,完成 markStack的创建;最后回到 dvmHeapStartup函数中,再完成了 cardTable的创建。
2.HeapBitmap 详解
2.1 HeapBitmap 怎么使用
我们在HeapBitmap的创建过程层中看到,虚拟机中一共创建了两个分别叫做 "dalvik-bitmap-1" 与 "dalvik-bitmap-2"的bitmap,他们对应的引用为HeapSource结构体的liveBits 和 markBits:
上面的注释,明确的告诉我们,这两个bitmap的作用:Struct HeapSource { ... /* * The live object bitmap. */ HeapBitmap liveBits; /* * The mark bitmap. */ HeapBitmap markBits; ... }
1.liveBits (live-bitmap): 表示上一次 GC后,存活下来的对象
2.markBits (mark-bitmap):将用来表示,本次GC被标记为存活的对象
这里的存活是指,这些对象被GcRoot直接或间接的引用了,从而被认为是这一次GC不应该回收这些对象;
我们可以在这里简单的想象一下GC的过程,当一次GC发生时,我们比对这两个bitmap,如果liveBits里标记对象A是存活的,而markBits标记对象A是需要被回收的(本次的扫描过程中,对象A没有被GcRoot直接或者间接的引用),那么对象A就是本次GC的回收对象之一;
这里有个问题,虚拟机启动后的第一次GC是会是怎样的呢?第一次的时候,liveBitmap应该是空的。
2.2 HeapBitmap如何来表示堆上的所有对象的存活状态
我们先来看一下HeapBitmap的数据结构:HeapBitmap中最重要的成员: unsigned long *bits; 这是一个unsigned long型数组,是否每个数据元素用来标记呢?答案是否定的;实际上是bits指向的这块内存中的每个byte的每一个bit来表示一个对象的存活状态;struct HeapBitmap { /* The bitmap data, which points to an mmap()ed area of zeroed * anonymous memory. */ unsigned long *bits; /* The size of the used memory pointed to by bits, in bytes. This * value changes when the bitmap is shrunk. */ size_t bitsLen; /* The real size of the memory pointed to by bits. This is the * number of bytes we requested from the allocator and does not * change. */ size_t allocLen; /* The base address, which corresponds to the first bit in * the bitmap. */ uintptr_t base; /* The highest pointer value ever returned by an allocation * from this heap. I.e., the highest address that may correspond * to a set bit. If there are no bits set, (max < base). */ uintptr_t max; };
注释中提的也不是特别明显,只是指出了这个数据指向被 mmap()出来的内存;
bitmap实际与object 的对应关系:
图1,bitmap部分选取了map中的8个bit,那么它将会对应着heap上8个对象的存活状态,比如图1中bitmap的前两个bit分别表示,Obj1是存活状态,Obj2是没有被GcRoot直接或者间接的引用,假如GcRoot只通过一个bitmap来确定对象是否回收,那么本次GC 将会回收 Obj2,Obj5,Obj8,其他对象存活。
那么虚拟机中的bitmap需要多大才能够表示heap上的所有对象的状态呢?比如heap上有N个对象,那么bitmap就至少需要 N个bit;接下来让我们做个乘除计算:我们要计算heap上最多能存在多少个对象,需要两个必要条件:1.heap的大小有多少个byte 2.每个object最少需要多少个byte其中必要条件1,由虚拟机的启动参数制定heap的大小,即明确了heap的大小为 heapsize;必要条件2,我们知道java中的类都默认继承于Object,那么每个类对象的大小一定是大于等于 Object类对象的大小,那么我们只需要使用Object对象的大小来计算即可;Object.java 对应于虚拟机中的 Struct Object,我们看下Object的构造:其包含一个指针和一个u4(4byte)成员,所以,Object的大小为 4+4 = 8 bytes;struct Object { /* ptr to class object */ ClassObject* clazz; /* * A word containing either a "thin" lock or a "fat" monitor. See * the comments in Sync.c for a description of its layout. */ u4 lock; };
bitmap需要N个bit,那么 N = heapsize/8, bitmap对应的 byte大小为 N/8 (heapsize/8/8);由于 bitmap使用 unsigned long数组表示,那么我们机选一下 bits数组的长度应该为多少:在32bit 架构上,unsigned long 一个元素的大小为 4 byte,即 4*8 = 32bit;所以bitmap 数组 bits的长度为 N/32 (heapsize/8/32)的时候,可以完全的标记heap上的所有对象;
举个栗子:32bit系统,heapsize 48M,一个bitmap需要占用((48*1024*1024)/8/8)= 786432 bytes = 768 kb;live-bitmap和 mark-bitmap 一起需要占用 2* 768 kb的空间;
我们看一下 dvmHeapBitmapInit 时,HeapBitmap的bits数组的长度分配:其中, maxSize 为heapsize(注意不是heapgrowthlimit), bitsLen为 HeapBitmap的元素 bits数组占用的字节数:bool dvmHeapBitmapInit(HeapBitmap *hb, const void *base, size_t maxSize, const char *name) { void *bits; size_t bitsLen; assert(hb != NULL); assert(name != NULL); bitsLen = HB_OFFSET_TO_INDEX(maxSize) * sizeof(*hb->bits); bits = dvmAllocRegion(bitsLen, PROT_READ | PROT_WRITE, name); if (bits == NULL) { ALOGE("Could not mmap %zd-byte ashmem region '%s'", bitsLen, name); return false; } hb->bits = (unsigned long *)bits; hb->bitsLen = hb->allocLen = bitsLen; hb->base = (uintptr_t)base; hb->max = hb->base - 1; return true; }
bitsLen = HB_OFFSET_TO_INDEX(maxSize) * sizeof(*hb->bits);
#define HB_OFFSET_TO_INDEX(offset_) \ ((uintptr_t)(offset_) / HB_OBJECT_ALIGNMENT / HB_BITS_PER_WORD) #define HB_OBJECT_ALIGNMENT 8 #define HB_BITS_PER_WORD (sizeof(unsigned long) * CHAR_BIT)
展开后: bitsLen = (maxSize/8/32) * 4;与我们前面计算的N/8(heapsize/8/8)相同;(heapsize与maxSize虚拟机配置的heapsize固定大小);
3.GcMarkStack详解
//TODO mark stack的数据结构,与使用方式
mark stack的Alloc:
if (!allocMarkStack(&gcHeap->markContext.stack, hs->maximumSize)) {
ALOGE("Can't create markStack");
dvmHeapBitmapDelete(&hs->markBits);
dvmHeapBitmapDelete(&hs->liveBits);
goto fail;
}
struct GcHeap {
...
/* The current state of the mark step.
* Only valid during a GC.
*/
GcMarkContext markContext;
...
}
/* This is declared publicly so that it can be included in gDvm.gcHeap.
*/
struct GcMarkContext {
HeapBitmap *bitmap;
GcMarkStack stack;
const char *immuneLimit;
const void *finger; // only used while scanning/recursing.
};
struct GcMarkStack {
/* Highest address (exclusive)
*/
const Object **limit;
/* Current top of the stack (exclusive)
*/
const Object **top;
/* Lowest address (inclusive)
*/
const Object **base;
/* Maximum stack size, in bytes.
*/
size_t length;
};
/*
* Create a stack big enough for the worst possible case, where the
* heap is perfectly full of the smallest object.
* TODO: be better about memory usage; use a smaller stack with
* overflow detection and recovery.
*/
static bool allocMarkStack(GcMarkStack *stack, size_t maximumSize)
{
const char *name = "dalvik-mark-stack";
void *addr;
assert(stack != NULL);
stack->length = maximumSize * sizeof(Object*) /
(sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD);
addr = dvmAllocRegion(stack->length, PROT_READ | PROT_WRITE, name);
if (addr == NULL) {
return false;
}
stack->base = (const Object **)addr;
stack->limit = (const Object **)((char *)addr + stack->length);
stack->top = NULL;
madvise(stack->base, stack->length, MADV_DONTNEED);
return true;
}
MarkStack可以简单理解为,是为了标记可达对象而创建的一个数据集合。在mark阶段,当标记完成一个 Object之后,我们要把这个Object push到MarkStack,后续会通过一个叫做 ProcessMarkStack的函数,来处理MarkStack上的Object,处理的方式是,依次标记 mark stack上记录的所有 object 的引用对象。
4.CardTable详解
//TODO 什么是card table,在GC的什么阶段使用
从上面,我们知道 Bitmap就是表示了整个 Heap的内存使用情况,每个bit 代表至少 8 字节的一个Object是否存活。
而CardTable这个数据结构是:在这个数据结构了,每一个 byte为一个元素,这个byte的数据可能是 0x70,0x6f ,0x0中的一个;每个 byte 代表着 heap上 128字节的地址范围,0x70代表其对应的 card是 Dirty的,0x0代表 Clean。在并行 GC 的时候,如果cardtable中一个 byte的值是 Dirty,说明在Mark阶段,它对应的 128 byte的数据被修改过,那么需要重新 Scan和标记这些对象。
Card Table 图解:
5.ModUnionTable
在ART中才有ModeUnionTable,总共有多个,每个ImageSpace有一个 ModeUnionTable,ZygoteSpace也有一个ModeUnionTbale。
ImageSpace的ModeUnionTable用来记录,从该IamgeSpace到 ZygoteSpace和到 AllocSpace的引用;
ZygoteSpace的ModUnionTable用来记录,从zygote space 到 AllocSpace的引用;
ModUnionTable的具体实现内容较多,见另外一篇:ART GC ModUnionTable 实现及使用