Dalvik虚拟机GC之 HeapBitmap ,GcMarkStack 与 CardTable

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hl09083253cy/article/details/54093513


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:
Struct HeapSource {
    ...
    /*
     * The live object bitmap.
     */
    HeapBitmap liveBits;

    /*
     * The mark bitmap.
     */
    HeapBitmap markBits;
    ...
}
上面的注释,明确的告诉我们,这两个bitmap的作用:
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的数据结构:
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;
};
HeapBitmap中最重要的成员: unsigned long *bits; 这是一个unsigned long型数组,是否每个数据元素用来标记呢?答案是否定的;实际上是bits指向的这块内存中的每个byte的每一个bit来表示一个对象的存活状态;
注释中提的也不是特别明显,只是指出了这个数据指向被 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的构造:
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;
};
其包含一个指针和一个u4(4byte)成员,所以,Object的大小为  4+4 = 8 bytes;
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数组的长度分配:
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;
}
其中, maxSize 为heapsize(注意不是heapgrowthlimit), bitsLen为 HeapBitmap的元素 bits数组占用的字节数:
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 实现及使用

阅读更多

没有更多推荐了,返回首页