Dalvik虚拟机为新创建对象分配内存,垃圾收集(GC)过程分析

 

 

     我们是不是想到再用一个Bitmap在描述上述第二个子阶段被修改的对象呢?虽然我们尽大努力减少了用来标记对象的Bitmap的大小,不过还是比较可观的。因此,为了减少内存的消耗,我们使用另外一种技术来标记Mark第二子阶段被修改的对象。这种技术使用到了一种称为Card Table的数据结构,如图4所示:

图4 Card Table

       从名字可以看出,Card Table由Card组成,一个Card实际上就是一个字节,它的值要么是CLEAN,要么是DIRTY。如果一个Card的值是CLEAN,就表示与它对应的对象在Mark第二子阶段没有被程序修改过。否则的话,就意味着被程序修改过,对于这些被修改过的对象。需要在Mark第二子阶段结束之后,再次禁止垃圾收集线程之外的其它线程执行,以便垃圾收集线程再次根据Card Table记录的信息对被修改过的对象引用的其它对象进行重新标记。由于Mark第二子阶段执行的时间不会太长,因此在该阶段被修改的对象不会很多,这样就可以保证第二次子阶段结束后,再次执行标记对象的过程是很快的,因而此时对程序造成的停顿非常小。

        在Card Table中,在连续GC_CARD_SIZE地址中的对象共用一个Card。Dalvik虚拟机将GC_CARD_SIZE的值设置为128。因此,假设堆的大小为Max Heap Size,那么我们只需要一块字节数为(Max Heap Size / 128)的Card Table。相比大小为(Max Heap Size / 8 / 32)× 4的Bitmap,减少了一半的内存需求。

 

 

     顾名思义,Mark-Sweep垃圾收集算法主要分为两个阶段:Mark和Sweep。Mark阶段从对象的根集开始标记被引用的对象。标记完成后,就进入到Sweep阶段,而Sweep阶段所做的事情就是回收没有被标记的对象占用的内存。这里涉及到的一个核心概念就是我们怎么标记对象有没有被引用的,换句说就是通过什么数据结构来描述对象有没有被引用。是的,就是图1中的Heap Bitmap了。Heap Bitmap的结构如图3所示:

图3 Heap Bitmap

        从名字就可以推断出,Heap Bitmap使用位图来标记对象是否被使用。如果一个对象被引用,那么在Bitmap中与它对应的那一位就会被设置为1。否则的话,就设置为0。在Dalvik虚拟机中,使用一个unsigned long数组来描述一个Heap Bitmap。我们假设堆的大小为Max Heap Size。我们使用libc提供的函数mspace_malloc来从堆里面分配内存时,得到的内存的地址总是对齐到HB_OBJECT_ALIGNMENT的。HB_OBJECT_ALIGNMENT的值等于8,也就是说,我们分配的对象的地址的最低三位总是0。这意味着我们在考虑Bitmap中的位与对象的对应关系时,可以不考虑这最低三位的值。这样可以大大地减少Bitmap的大小。例如,在32位设备上,每一个对象的地址都有32位,除去最低的三位,我们在考虑Bitmap与对象的对应关系时,只需要考虑高29位就可以了。因此,在计算所需要的Bitmap的大小时,就可以将堆的大小值除以HB_OBJECT_ALIGNMENT,即除以8。

        假设一个unsigned long数占HB_BITS_PER_WORD个位,那么,我们就需要一个大小为(Max Heap Size /  HB_OBJECT_ALIGNMENT / HB_BITS_PER_WORD)的unsigned long数组来描述一个大小为Max Heap Size的堆。在32位设备上,一个unsigned long数占用32位,即HB_BITS_PER_WORD的值等于32。综合上面的描述,我们就可以知道,一个大小为Max Heap Size的堆需要一个大小为(Max Heap Size / 8 / 32)的unsigned long数组来描述,即需要一块字节数等于(Max Heap Size / 8 / 32)× 4的内存来描述。

        在图1中,我们使用了两个Bitmap来描述堆的对象,一个称为Live Bitmap,另一个称为Mark Bitmap。Live Bitmap用来标记上一次GC时被引用的对象,也就是没有被回收的对象,而Mark Bitmap用来标记当前GC有被引用的对象。有了这两个信息之后,我们就可以很容易地知道哪些对象是需要被回收的,即在Live Bitmap在标记为1,但是在Mark Bitmap中标记为0的对象。

 Dalvik虚拟机为新创建对象分配内存的过程分析

 

 

dvmAllocObject

clazz           dvmAllocObject


dvmThreadSelf Thread* self

DDMS         GC

gcRunning

dvmWaitForConcurrentGcToComplete

dvmHeapSourceAlloc

就需要调用函数gcForMalloc来执行一次GC了,参数false表示不要回收软引用对象引用的对象。


uintptr_t                             size_t(字节)

mspace_calloc heap->msp    ,1, n

       从前面Dalvik虚拟机Java堆创建过程分析一文可知,gHs是一个全局变量,它指向一个HeapSource结构。在这个HeapSource结构中,有一个heaps数组,其中第一个元素描述的是Active堆,第二个元素描述的是Zygote堆。

       通过宏hs2heap可以获得HeapSource结构中的Active堆,保存在本地变量heap中。宏hs2heap的实现如下所示:

#define hs2heap(hs_) (&((hs_)->heaps[0]))

HeapSource

Active堆的内存碎片就会越来越来重。相反,如果我们施加一个Soft   
Zygote

Zygote堆

madvise        mspace_malloc

 
 Zygote堆和 Active堆的内存碎片就会越来越来重。相反,如果我们施加一个Soft
 
 
 
heapAllocAndGrow

HeapSource* Heap* 


mspace_footprint

Active堆的内存碎片就会越来越来重。相反,如果我们施加一个Soft


GcSpec    gcForMalloc


clearSoftReference


trimHeaps
dvmCollectGarbageInternal


GC_CONCURRENT


System.gc     VMRuntime


gHs.gcThreadCond

dvmCollectGarbage


Susspe

GcSpec


void dvmCollectGarbageInternal(const GcSpec* spec)
{
    ......
 
    if (gcHeap->gcRunning) {
        ......
        return;
    }
    ......
 
    gcHeap->gcRunning = true;
    ......
 
    dvmSuspendAllThreads(SUSPEND_FOR_GC); //SUSPEND_FOR_GC
    ......
 
    if (!dvmHeapBeginMarkStep(spec->isPartial)) {//dvmUnlockHeap
        ......
        dvmAbort();
    }
 
    dvmHeapMarkRootSet();
    ......
 
    if (spec->isConcurrent) {
        ......
        dvmClearCardTable();
        dvmUnlockHeap();
        dvmResumeAllThreads(SUSPEND_FOR_GC);
        ......
    }
 
    dvmHeapScanMarkedObjects();//递归标记
 
    if (spec->isConcurrent) {
        ......
        dvmLockHeap();
        ......
        dvmSuspendAllThreads(SUSPEND_FOR_GC);
        ......
        dvmHeapReMarkRootSet();
        ......
        dvmHeapReScanMarkedObjects();
    }
 
    dvmHeapProcessReferences(&gcHeap->softReferences,
                             spec->doPreserve == false,
                             &gcHeap->weakReferences,
                             &gcHeap->finalizerReferences,
                             &gcHeap->phantomReferences);
    ......
 
    dvmHeapSweepSystemWeaks();
    ......
   
    dvmHeapSourceSwapBitmaps();
    .......
 
    if (spec->isConcurrent) {
        dvmUnlockHeap();
        dvmResumeAllThreads(SUSPEND_FOR_GC);
        ......
    }
    dvmHeapSweepUnmarkedObjects(spec->isPartial, spec->isConcurrent,
                                &numObjectsFreed, &numBytesFreed);
    ......
    dvmHeapFinishMarkStep();
    if (spec->isConcurrent) {
        dvmLockHeap();
    }
    ......
 
    dvmHeapSourceGrowForUtilization();
    ......
 
    gcHeap->gcRunning = false;
    ......
 
    if (spec->isConcurrent) {
        ......
        dvmBroadcastCond(&gDvm.gcHeapCond);
    }
 
    if (!spec->isConcurrent) {
        dvmResumeAllThreads(SUSPEND_FOR_GC);
        ......
    }
 
    .......
    dvmEnqueueClearedReferences(&gDvm.gcHeap->clearedReferences);
 
    ......
}

dvmHeapScanMarkedObjects

并且使用这个Card Table来记录那些在GC过程中被修改的对象。

dvmBroadcastCond

     在以下三种情况下,当前线程需要挂起其它线程:

        1. 执行GC。

        2. 调试器请求。

        3. 发生调试事件,如断点命中。


void dvmSuspendAllThreads(SuspendCause why)
{
    Thread* self = dvmThreadSelf();
    Thread* thread;
    ......
 
    lockThreadSuspend("susp-all", why);//就表明当前线程可以执行挂起其它线程的操作了。
    ......
 
    dvmLockThreadList(self);
 
    lockThreadSuspendCount();
    for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
        if (thread == self)
            continue;
 
        /* debugger events don't suspend JDWP thread */
        if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) &&
            thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
            continue;//JDWP            
 
        dvmAddToSuspendCounts(thread, 1,
                              (why == SUSPEND_FOR_DEBUG ||
                              why == SUSPEND_FOR_DEBUG_EVENT)
                              ? 1 : 0);
    }
    unlockThreadSuspendCount();
 
    for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
        if (thread == self)
            continue;
 
        /* debugger events don't suspend JDWP thread */
        if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) &&
            thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
            continue;
 
        /* wait for the other thread to see the pending suspend */
        waitForThreadSuspend(self, thread);
        ......
    }
 
    dvmUnlockThreadList();
    unlockThreadSuspend();
 
    ......
}


waitForThreadSuspend

createMarkStack                                


static bool createMarkStack(GcMarkStack *stack)
{
    assert(stack != NULL);
    size_t length = dvmHeapSourceGetIdealFootprint() * sizeof(Object*) /
        (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD);
        
        
        dvmHeapSourceGetIdealFootprint() 总字节数-maximumSize描述的是Java堆的大小=
偏移(地址)值    offset = maximumSize

        sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD //一个Object对象占用 sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD
        个字节
        
        
        (maximumSize / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))个对象
        
        
        
        参数maximumSize描述的是Java堆的大小。
        
        
        每个对象占sizeof(Object*)个字节
        
    madvise(stack->base, length, MADV_NORMAL);
    stack->top = stack->base;
    return true;
}

GcMarkStack


一个大小为maximumSize的Java堆,在最坏情况下,存在(maximumSize / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))个对象。

在最坏情况下,存在(maximumSize / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))个对象。也就是说,GcMarkStack通过一个大小为(maximumSize / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))的Object*数组来描述一个栈。如果换成字节数来描述的话,就是说我们需要一块大小为(maximumSize * sizeof(Object*) / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))的内存块来描述一个GcMarkStack栈。


 stack->length = maximumSize * sizeof(Object*) /
        (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD);
        

        
        

  回到函数allocMarkStack中,我们分析一下需要一个多大的栈来描述Java堆的所有对象。首先,每一个Java对象都是必须要从Object结构体继承下来的,这意味着每一个Java对象占用的内存都至少为sizeof(Object)。其次,通过C库提供的接口mspace_malloc在Java堆上为对象分配内存时,C库自己需要一些额外的内存来管理该块内存,例如用额外的4个字节来记录分配出去的内存块的大小。额外需要的内存大小通过宏HEAP_SOURCE_CHUNK_OVERHEAD来描述。最后,我们就可以知道,一个大小为maximumSize的Java堆,在最坏情况下,存在(maximumSize / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))个对象。也就是说,GcMarkStack通过一个大小为(maximumSize / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))的Object*数组来描述一个栈。如果换成字节数来描述的话,就是说我们需要一块大小为(maximumSize * sizeof(Object*) / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))的内存块来描述一个GcMarkStack栈。

#define HB_OFFSET_TO_BYTE_INDEX(offset_) \
  (HB_OFFSET_TO_INDEX(offset_) * sizeof(*((HeapBitmap *)0)->bits))


偏移(地址)值    offset = maximumSize

void dvmMarkImmuneObjects(const char *immuneLimit)
{
    ......
 
    for (size_t i = 1; i < gHs->numHeaps; ++i) {
        if (gHs->heaps[i].base < immuneLimit) {
            assert(gHs->heaps[i].limit <= immuneLimit);
            /* Compute the number of words to copy in the bitmap. */
            size_t index = HB_OFFSET_TO_INDEX(
                (uintptr_t)gHs->heaps[i].base - gHs->liveBits.base);
            /* Compute the starting offset in the live and mark bits. */
            char *src = (char *)(gHs->liveBits.bits + index);
            char *dst = (char *)(gHs->markBits.bits + index);
            /* Compute the number of bytes of the live bitmap to copy. */
            size_t length = HB_OFFSET_TO_BYTE_INDEX(
                gHs->heaps[i].limit - gHs->heaps[i].base);
            /* Do the copy. */
            memcpy(dst, src, length);
            /* Make sure max points to the address of the highest set bit. */
            if (gHs->markBits.max < (uintptr_t)gHs->heaps[i].limit) {
                gHs->markBits.max = (uintptr_t)gHs->heaps[i].limit;
            }
        }
    }
}

  size_t length = HB_OFFSET_TO_BYTE_INDEX(
                gHs->heaps[i].limit - gHs->heaps[i].base);
            /* Do the copy. */
            memcpy(dst, src, length);
            
            
        
void dvmMarkImmuneObjects(const char *immuneLimit)
{
    ......
 
    for (size_t i = 1; i < gHs->numHeaps; ++i) {
        if (gHs->heaps[i].base < immuneLimit) {
            assert(gHs->heaps[i].limit <= immuneLimit)    
            
const char* immuneLimit

前面分析的函数dvmHeapSourceGetImmuneLimit可以知道,参数immuneList要么是等于Zygote堆的最大地址值,要么是等于NULL。这取决于当前GC要执行的是全部垃圾回收还是部分垃圾回收。


   size_t index = HB_OFFSET_TO_INDEX(
                (uintptr_t)gHs->heaps[i].base - gHs->liveBits.base);
            /* Compute the starting offset in the live and mark bits. */
            char *src = (char *)(gHs->liveBits.bits + index);
            char *dst = (char *)(gHs->markBits.bits + index);

            HB_OFFSET_TO_INDEX =存在(maximumSize / (sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD))个对象

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;
};
       这个结构体定义在文件dalvik/vm/alloc/HeapBitmap.h。
       代码对HeapBitmap结构体的各个成员变量的含义已经有很详细的注释,其中最重要的就是成员变量bits指向的一个类型为unsigned long的数组,这个数组的每一个bit都用来标记一个对象是否存活。

       回到函数dvmHeapBitmapInit中,Java堆的起始地址为base,大小为maxSize,由此我们就知道,在Java堆上创建的对象的地址范围为[base, maxSize)。但是通过C库提供的mspace_malloc来在Java堆分配内存时,得到的内存地址是以8字节对齐的。这意味着我们只需要(maxSize / 8)个bit来描述Java堆的对象。结构体HeapBitmap的成员变量bits是一个类型为unsigned long的数组,也就是说,数组中的每一个元素都可以描述sizeof(unsigned long)个对象的存活。在32位设备上,一个unsigned long占用32个bit,这意味着需要一个大小为(maxSize / 8 / 32)的unsigned long数组来描述Java堆对象的存活。如果换成字节数来描述的话,就是说我们需要一块大小为(maxSize / 8 / 32) × 4的内存块来描述一个大小为maxSize的Java堆对象。

      Dalvik虚拟机提供了一些宏来描述对象地址与HeapBitmap结构体的成员变量bits所描述的unsigned long数组的关系,如下所示:

#define HB_OBJECT_ALIGNMENT 8
#define HB_BITS_PER_WORD (sizeof(unsigned long) * CHAR_BIT)
 
/* <offset> is the difference from .base to a pointer address.
 * <index> is the index of .bits that contains the bit representing
 *         <offset>.
 */
#define HB_OFFSET_TO_INDEX(offset_) \
    ((uintptr_t)(offset_) / HB_OBJECT_ALIGNMENT / HB_BITS_PER_WORD)
#define HB_INDEX_TO_OFFSET(index_) \
    ((uintptr_t)(index_) * HB_OBJECT_ALIGNMENT * HB_BITS_PER_WORD)
 
#define HB_OFFSET_TO_BYTE_INDEX(offset_) \
  (HB_OFFSET_TO_INDEX(offset_) * sizeof(*((HeapBitmap *)0)->bits))
       这些宏定义在文件dalvik/vm/alloc/HeapBitmap.h中。
       假设我们知道了一个对象的地址为ptr,Java堆的起始地址为base,那么就可以计算得到一个偏移值offset。有了这个偏移值之后,就可以通过宏HB_OFFSET_TO_INDEX计算得到用来描述该对象存活的bit位于HeapBitmap结构体的成员变量bits所描述的unsigned long数组的索引index。有了这个index之后,我们就可以得到一个unsigned long值。接着再通过对象地址ptr的第4到第8位表示的数值为索引,在前面找到的unsigned long值取出相应的位,就可以得到该对象是否存活了。

       相反,给出一个HeapBitmap结构体的成员变量bits所描述的unsigned long数组的索引index,我们可以通过宏HB_INDEX_TO_OFFSET找到一个偏移值offset,将这个偏移值加上Java堆的起始地址base,就可以得到一个Java对象的地址ptr。

       第三个宏HB_OFFSET_TO_BYTE_INDEX借助宏HB_OFFSET_TO_INDEX来找出用来描述对象存活的bit在HeapBitmap结构体的成员变量bits所描述的内存块的字节索引。

       有了上述的基础知识之后,函数dvmHeapBitmapInit的实现就一目了然了。


HeapBitmap

uintptr_t


/* Types for `void *' pointers.  */
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int        intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned long int    uintptr_t;
#else
# ifndef __intptr_t_defined
typedef int            intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned int        uintptr_t;
#endif
在64位的机器上,intptr_t和uintptr_t分别是long int、unsigned long int的别名;在32位的机器上,intptr_t和uintptr_t分别是int、unsigned int的别名。

char* immuneLimit

Live Bitmap。执行了前面的13步之后,所有还被引用的对象在Mark 

Mark Bitmap。执行了前面的13步之后,所有还被引用的对象在Mark


loadedClasses        Hash

visitHashTable        dbgRegistry

jniGlobalRefTable

GetStringUTFChars      GetByteArrayElements等接口访问字符串或者数组时被Pin住的对象。这些对象保存在gDvm


visitThread

RootVisitor *visitor

Thread* thread  void * arg


visitThreadStack     

saveArea->prevFrame

const u1* regVector

    u4 threadId = thread->threadId;


Local Reference Table。关于Card


dex- odex

RegisterMap*


       注意,函数visitThreadStack按照字节来访问寄存器向量regVector。每次读出1个字节的数据到变量bits的0~7位,然后再将变量bits的第8位设置为1。此后从低位开始,每访问一位就将变量bits向右移一位。当向左移够8位后,就能保证变量bits的值变为1,这时候就再从寄存器向量regVector读取下一字节进行遍历。

Mark Stack。这个是针对前面第2步的操作。

      在Card Table中,在连续GC_CARD_SIZE地址中的对象共用一个Card。Dalvik虚拟机将GC_CARD_SIZE的值设置为128。因此,假设堆的大小为Max Heap Size,那么我们只需要一块字节数为(Max Heap Size / 128)的Card Table。相比大小为(Max Heap Size / 8 / 32)× 4的Bitmap,减少了一半的内存需求。

      GC_CARD_SIZE = 128
      
      
           从名字可以看出,Card Table由Card组成,一个Card实际上就是一个字节,它的值要么是CLEAN,要么是DIRTY。如果一个Card的值是CLEAN,就表示与它对应的对象在Mark第二子阶段没有被程序修改过。否则的话,就意味着被程序修改过,对于这些被修改过的对象。需要在Mark第二子阶段结束之后,再次禁止垃圾收集线程之外的其它线程执行,以便垃圾收集线程再次根据Card Table记录的信息对被修改过的对象引用的其它对象进行重新标记。由于Mark第二子阶段执行的时间不会太长,因此在该阶段被修改的对象不会很多,这样就可以保证第二次子阶段结束后,再次执行标记对象的过程是很快的,因而此时对程序造成的停顿非常小。

        在Card Table中,在连续GC_CARD_SIZE地址中的对象共用一个Card。Dalvik虚拟机将GC_CARD_SIZE的值设置为128。因此,假设堆的大小为Max Heap Size,那么我们只需要一块字节数为(Max Heap Size / 128)的Card Table。相比大小为(Max Heap Size / 8 / 32)× 4的Bitmap,减少了一半的内存需求。

        128个对象共用一 字节card
        
        
        
void dvmClearCardTable()
{
    ......
 
    if (gDvm.lowMemoryMode) {
      // zero out cards with madvise(), discarding all pages in the card table
      madvise(gDvm.gcHeap->cardTableBase, gDvm.gcHeap->cardTableLength, MADV_DONTNEED);
    } else {
      // zero out cards with memset(), using liveBits as an estimate
      const HeapBitmap* liveBits = dvmHeapSourceGetLiveBits();
      size_t maxLiveCard = (liveBits->max - liveBits->base) / GC_CARD_SIZE;
      maxLiveCard = ALIGN_UP_TO_PAGE_SIZE(maxLiveCard);
      if (maxLiveCard > gDvm.gcHeap->cardTableLength) {
          maxLiveCard = gDvm.gcHeap->cardTableLength;
      }
 
      memset(gDvm.gcHeap->cardTableBase, GC_CARD_CLEAN, maxLiveCard);
    }
}

dvmClearCardTable

memset()函数原型是extern void *memset(void *buffer, int c, int count)        buffer:为指针或是数组,
              c:是赋给buffer的值,

       count:是buffer的长度.


       这个函数在socket中多用于清空数组.如:原型是memset(buffer, 0, sizeof(buffer))

       Memset 用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘ ’或‘/0’;

      例:char a[100];memset(a, '/0', sizeof(a));

    memset可以方便的清空一个结构类型的变量或数组。

如:


dvmHeapBitmapScanWalk

void dvmHeapBitmapScanWalk(HeapBitmap *bitmap,
                           BitmapScanCallback *callback, void *arg)
{
    ......
    uintptr_t end = HB_OFFSET_TO_INDEX(bitmap->max - bitmap->base);
    uintptr_t i;
    for (i = 0; i <= end; ++i) {
        unsigned long word = bitmap->bits[i];
        if (UNLIKELY(word != 0)) {
            unsigned long highBit = 1 << (HB_BITS_PER_WORD - 1);
            uintptr_t ptrBase = HB_INDEX_TO_OFFSET(i) + bitmap->base;
            void *finger = (void *)(HB_INDEX_TO_OFFSET(i + 1) + bitmap->base);
            while (word != 0) {
                const int shift = CLZ(word);
                Object *obj = (Object *)(ptrBase + shift * HB_OBJECT_ALIGNMENT);
                (*callback)(obj, finger, arg);
                word &= ~(highBit >> shift);
            }
            end = HB_OFFSET_TO_INDEX(bitmap->max - bitmap->base);
        }
    }
}

      函数dvmHeapBitmapScanWalk遍历Mark Bitmap中值等于1的位,并且调用参烽callback指向的函数来标记它们引用的对象。在遍历的过程中,主要用到了HB_BITS_PER_WORD、HB_INDEX_TO_OFFSET、HB_OBJECT_ALIGNMENT和HB_OFFSET_TO_INDEX这四个宏,它们的具体实现可以参考前面Dalvik虚拟机Java堆创建过程分析一文。
      
      
      
#define HB_OBJECT_ALIGNMENT 8
#define HB_BITS_PER_WORD (sizeof(unsigned long) * CHAR_BIT)


Object* obj = (Object*) (ptrBase + shift * HB_OBJECT_ALIGNMENT);


     参数obj指向要遍历的对象,finger指向当前遍历的Java对象的最大地址,arg指向一个GcMarkContext结构体。Java对象是按照地址值从小到大的顺序遍历的,因此这里传进来的参数finger的值也是递增的。
     
     Live Bitmap。执行了前面的13步之后,所有还被引用的对象在Mark 
     
     
     Mark Bitmap。执行了前面的13步之后,所有还被引用的对象在Mark 
     
     Mark Stack。这个是针对前面第2步的操作。
     
     
     softReferences  weakReferences finalizerReferences 
     phantomReferences
     enqueuePendingReference
     MarkSweep.cpp。


1. 在遍历Mark Bitmap之前,根集对象已经被标记;

        2. 遍历Mark Bitmap是按地址值从小到大的顺序遍历的。

        这意味着,遍历完成Mark Bitmap之后,以下对象都是已经被标记的:

        1. 根集对象;

        2. 被根集对象直接或者间接引用并且地址值比当前遍历的对象还要大的对象。
        
        
        C 库函数 void *memchr(const void *str, int c, size_t n) 在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字符)的位置。
        
        
        memchr      str c  n   
        
        
        虽然每GC_CARD_SIZE个对象共用一个Dirty Card,但是这些对象不一定会存在
        
        
        
        SoftReference、WeakReference、FinalizerReference和PhantomReference类型的引用对象分别被保存在了gcHeap-
        
        
        preserveSomeSoftReferences Object **list
        
           只有那些之前没有被标记过的,并且被SoftReference和WeakReference引用的对象才会被处理。处理过程如下所示:

        1. 调用函数clearReference函数切断引用对象和被引用对象的关系,也就是将引用对象的成员变量referent设置为NULL。

        2. 如果引用对象在创建的时候关联有队列,那么就调用函数enqueueReference将其加入到关联队列中去,以便应用程序可以知道哪些引用对象引用的对象被GC回收了。

        再回到函数dvmHeapProcessReferences中,处理完成SoftReference和WeakReference引用之后。接着再调用函数enqueueFinalizerReferences处理FinalizerReference引用。

        函数enqueueFinalizerReferences的实现如下所示:
        
        
        
        live  & ~
        
        111111111111        
        
        
        void dvmHeapBitmapSweepWalk(const HeapBitmap *liveHb, const HeapBitmap *markHb,
                            uintptr_t base, uintptr_t max,
                            BitmapSweepCallback *callback, void *callbackArg)
{
    ......
    void *pointerBuf[4 * HB_BITS_PER_WORD];
    void **pb = pointerBuf;
    size_t start = HB_OFFSET_TO_INDEX(base - liveHb->base);
    size_t end = HB_OFFSET_TO_INDEX(max - liveHb->base);
    unsigned long *live = liveHb->bits;
    unsigned long *mark = markHb->bits;
    for (size_t i = start; i <= end; i++) {
        unsigned long garbage = live[i] & ~mark[i];
        if (UNLIKELY(garbage != 0)) {
            unsigned long highBit = 1 << (HB_BITS_PER_WORD - 1);
            uintptr_t ptrBase = HB_INDEX_TO_OFFSET(i) + liveHb->base;
            while (garbage != 0) {
                int shift = CLZ(garbage);
                garbage &= ~(highBit >> shift);
                *pb++ = (void *)(ptrBase + shift * HB_OBJECT_ALIGNMENT);
            }
            /* Make sure that there are always enough slots available */
            /* for an entire word of 1s. */
            if (pb >= &pointerBuf[NELEM(pointerBuf) - HB_BITS_PER_WORD]) {
                (*callback)(pb - pointerBuf, pointerBuf, callbackArg);
                pb = pointerBuf;
            }
        }
    }
    if (pb > pointerBuf) {
        (*callback)(pb - pointerBuf, pointerBuf, callbackArg);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值