<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                [原文出處-------------Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063) 使用C/C++開發應用程序最令頭痛的問題就是內存管理。慎不留神,要么內存泄漏,要么內存破壞。虛擬機要解決的問題之一就是幫助應用程序自動分配和釋放內存。為了達到這個目的,虛擬機在啟動的時候向操作系統申請一大塊內存當作對象堆。之后當應用程序創建對象時,虛擬機就會在堆上分配合適的內存塊。而當對象不再使用時,虛擬機就會將它占用的內存塊歸還給堆。Dalvik虛擬機也不例外,本文就分析它的Java堆創建過程。 從前面Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃一文可以知道,在Dalvik虛擬機中,Java堆實際上是由一個Active堆和一個Zygote堆組成的,如圖1所示: ![](https://box.kancloud.cn/7a2c3e699e896ad83b01f8af3b542517_627x353.png) :-: 圖1 Dalvik虛擬機的Java堆 其中,Zygote堆用來管理Zygote進程在啟動過程中預加載和創建的各種對象,而Active堆是在Zygote進程fork第一個子進程之前創建的。之后無論是Zygote進程還是其子進程,都在Active堆上進行對象分配和釋放。這樣做的目的是使得Zygote進程和其子進程最大限度地共享Zygote堆所占用的內存。 為了管理Java堆,Dalvik虛擬機需要一些輔助數據結構,包括一個Card Table、兩個Heap Bitmap和一個Mark Stack。Card Table是為了記錄在垃圾收集過程中對象的引用情況的,以便可以實現Concurrent G。圖1的兩個Heap Bitmap,一個稱為Live Heap Bitmap,用來記錄上次GC之后,還存活的對象,另一個稱為Mark Heap Bitmap,用來記錄當前GC中還存活的對象。這樣,上次GC后存活的但是當前GC不存活的對象,就是需要釋放的對象。Davlk虛擬機使用標記-清除(Mark-Sweep)算法進行GC。在標記階段,通過一個Mark Stack來實現遞歸檢查被引用的對象,即在當前GC中存活的對象。有了這個Mark Stack,就可以通過循環來模擬函數遞歸調用。 Dalvik虛擬機Java堆的創建過程實際上就是上面分析的各種數據結構的創建過程,它們是在Dalvik虛擬機啟動的過程中創建的。接下來,我們就詳細分析這個過程。 從前面Dalvik虛擬機的啟動過程分析一文可以知道,Dalvik虛擬機在啟動的過程中,會通過調用函數dvmGcStartup來創建Java堆,它的實現如下所示: ~~~ bool dvmGcStartup() { dvmInitMutex(&gDvm.gcHeapLock); pthread_cond_init(&gDvm.gcHeapCond, NULL); return dvmHeapStartup(); } ~~~ 這個函數定義在文件dalvik/vm/alloc/Alloc.cpp中。 函數dvmGcStartup首先是分別初始化一個鎖和一個條件變量,它們都是用來保護堆的并行訪問的,接著再調用另外一個函數dvmHeapStartup來創建Java堆。 函數dvmHeapStartup的實現如下所示: ~~~ bool dvmHeapStartup() { GcHeap *gcHeap; if (gDvm.heapGrowthLimit == 0) { gDvm.heapGrowthLimit = gDvm.heapMaximumSize; } gcHeap = dvmHeapSourceStartup(gDvm.heapStartingSize, gDvm.heapMaximumSize, gDvm.heapGrowthLimit); ...... gDvm.gcHeap = gcHeap; ...... if (!dvmCardTableStartup(gDvm.heapMaximumSize, gDvm.heapGrowthLimit)) { LOGE_HEAP("card table startup failed."); return false; } return true; } ~~~ 這個函數定義在文件dalvik/vm/alloc/Heap.cpp中。 gDvm是一個類型為DvmGlobals的全局變量,它通過各個成員變量記錄了Dalvik虛擬機的各種信息。這里涉及到三個重要與Java堆相關的信息,分別是Java堆的起始大小(Starting Size)、最大值(Maximum Size)和增長上限值(Growth Limit)。在啟動Dalvik虛擬機的時候,我們可以分別通過-Xms、-Xmx和-XX:HeapGrowthLimit三個選項來指定上述三個值。 Java堆的起始大小(Starting Size)指定了Davlik虛擬機在啟動的時候向系統申請的物理內存的大小。后面再根據需要逐漸向系統申請更多的物理內存,直到達到最大值(Maximum Size)為止。這是一種按需要分配策略,可以避免內存浪費。在默認情況下,Java堆的起始大小(Starting Size)和最大值(Maximum Size)等于4M和16M。但是廠商會通過dalvik.vm.heapstartsize和dalvik.vm.heapsize這兩個屬性將它們設置為合適設備的值的。 注意,雖然Java堆使用的物理內存是按需要分配的,但是它使用的虛擬內存的總大小卻是需要在Dalvik啟動的時候就確定的。這個虛擬內存的大小就等于Java堆的最大值(Maximum Size)。想象一下,如果不這樣做的話,會出現什么情況。假設開始時創建的虛擬內存小于Java堆的最大值(Maximum Size),由于實際情況是允許虛擬內存的大小是達到Java堆的最大值(Maximum Size)的,因此,當開始時創建的虛擬內存無法滿足需求時,那么就需要重新創建另外一塊更大的虛擬內存。這樣就需要將之前的虛擬內存的內容拷貝到新創建的更大的虛擬內存去,并且還要相應地修改各種輔助數據結構。這樣太麻煩了,而且效率也太低了。因此就在一開始的時候,就創建一塊與Java堆的最大值(Maximum Size)相等的虛擬內存。 但是,Dalvik虛擬機又希望能夠動態地調整Java堆的可用最大值,于是就出現了一個稱為增長上限的值(Growth Limit)。這個增長上限值(Growth Limit),我們可以認為它是Java堆大小的軟限制,而前面所描述的最大值(Maximum Size),是Java堆大小的硬限制。通過動態地調整增長上限值(Growth Limit),就可以實現動態調整Java堆的可用最大值,但是這個增長上限值必須要小于等于最大值(Maximum Size)。從函數dvmHeapStartup的實現可以知道,如果沒有指定Java堆的增長上限的值(Growth Limit),那么它的值就等于Java堆的最大值(Maximum Size)。 事實上,在全局變量gDvm中,除了上面提到的三個信息之外,還有三種信息是與Java堆相關的,它們分別是堆最小空閑值(Min Free)、堆最大空閑值(Max Free)和堆目標利用率(Target Utilization)。這三個值可以分別通過Dalvik虛擬機的啟動選項-XX:HeapMinFree、-XX:HeapMaxFree和-XX:HeapTargetUtilization來指定。它們用來確保每次GC之后,Java堆已經使用和空閑的內存有一個合適的比例,這樣可以盡量地減少GC的次數。舉個例子說,堆的利用率為U,最小空閑值為MinFree字節,最大空閑值為MaxFree字節。假設在某一次GC之后,存活對象占用內存的大小為LiveSize。那么這時候堆的理想大小應該為(LiveSize / U)。但是(LiveSize / U)必須大于等于(LiveSize + MinFree)并且小于等于(LiveSize + MaxFree)。 了解了這些與Java堆大小相關的信息之后,我們回到函數dvmGcStartup中,可以清楚看到,它先是調用函數dvmHeapSourceStartup來創建一個Java堆,接著再調用函數dvmCardTableStartup來為該Java堆創建一個Card Table。接下來我們先分析函數dvmHeapSourceStartup的實現,接著再分析函數dvmCardTableStartup的實現。 函數dvmHeapSourceStartup的實現如下所示: ~~~ GcHeap* dvmHeapSourceStartup(size_t startSize, size_t maximumSize, size_t growthLimit) { GcHeap *gcHeap; HeapSource *hs; mspace msp; size_t length; void *base; ...... /* * 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"); ...... /* Create an unlocked dlmalloc mspace to use as * a heap source. */ msp = createMspace(base, kInitialMorecoreStart, startSize); ...... gcHeap = (GcHeap *)calloc(1, sizeof(*gcHeap)); ...... hs = (HeapSource *)calloc(1, sizeof(*hs)); ...... hs->targetUtilization = gDvm.heapTargetUtilization * HEAP_UTILIZATION_MAX; hs->minFree = gDvm.heapMinFree; hs->maxFree = gDvm.heapMaxFree; hs->startSize = startSize; hs->maximumSize = maximumSize; hs->growthLimit = growthLimit; ...... hs->numHeaps = 0; ...... hs->heapBase = (char *)base; hs->heapLength = length; ...... if (!addInitialHeap(hs, msp, growthLimit)) { ...... } if (!dvmHeapBitmapInit(&hs->liveBits, base, length, "dalvik-bitmap-1")) { ...... } if (!dvmHeapBitmapInit(&hs->markBits, base, length, "dalvik-bitmap-2")) { ...... } if (!allocMarkStack(&gcHeap->markContext.stack, hs->maximumSize)) { ...... } gcHeap->markContext.bitmap = &hs->markBits; gcHeap->heapSource = hs; gHs = hs; return gcHeap; ...... } ~~~ 這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。 函數dvmHeapSourceStartup的執行過程如下所示: 1. 將參數maximum指定的最大堆大小對齊到內存頁邊界,得到結果為length,并且調用函數dvmAllocRegion分配一塊大小等于length的匿名共享內存塊,起始地址為base。這塊匿名共享內存即作為Dalvik虛擬機的Java堆。 2. 調用函數createMspace將前面得到的匿名共享內存塊封裝為一個mspace,以便后面可以通過C庫得供的mspace_malloc和mspace_bulk_free等函數來管理Java堆。這個mspace的起始大小為Java堆的起始大小,這意味著一開始在該mspace上能夠分配的內存不能超過Java堆的起始大小。不過后面我們動態地調整這個mspace的大小,使得它可以使用更多的內存,但是不能超過Java堆的最大值。 3, 分配一個GcHeap結構體gcHeap和一個HeapSource結構體hs,用來維護Java堆的信息,包括Java堆的目標利用率、最小空閑值、最大空閑值、起始大小、最大值、增長上限值、堆個數、起始地址和大小等信信息。 4. 調用函數addInitialHeap在前面得到的匿名共享內存上創建一個Active堆。這個Active堆的最大值被設置為Java堆的起始大小。 5. 調用函數dvmHeapBitmapInit創建和初始化一個Live Bitmap和一個Mark Bitmap,它們在GC時會用得到。 6. 調用函數allockMarkStack創建和初始化一個Mark Stack,它在GC時也會用到。 7. 將前面創建和初始化好的Mark Bitmap和HeapSource結構體hs保存在前面創建的GcHeap結構體gcHeap中,并且將該GcHeap結構體gcHeap返回給調用者。同時,HeapSource結構體hs也會保存在全局變量gHs中。 為了更好地對照圖2來理解函數dvmHeapSourceStartup所做的事情,接下來我們詳細分析上述提到的關鍵函數dvmAllocRegion、createMspace、addInitialHeap、dvmHeapBitmapInit和allockMarkStack的實現。 函數dvmAllocRegion的實現如下所示: ~~~ void *dvmAllocRegion(size_t byteCount, int prot, const char *name) { void *base; int fd, ret; byteCount = ALIGN_UP_TO_PAGE_SIZE(byteCount); fd = ashmem_create_region(name, byteCount); if (fd == -1) { return NULL; } base = mmap(NULL, byteCount, prot, MAP_PRIVATE, fd, 0); ret = close(fd); if (base == MAP_FAILED) { return NULL; } if (ret == -1) { munmap(base, byteCount); return NULL; } return base; } ~~~ 這個函數定義在文件dalvik/vm/Misc.cpp中。 從這里就可以清楚地看出,函數dvmAllocRegion所做的事情就是調用函數ashmem_create_region來創建一塊匿名共享內存。關于Android系統的匿名共享內存,可以參考前面Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃一文。 函數createMspace的實現如下所示: ~~~ static mspace createMspace(void* begin, size_t morecoreStart, size_t startingSize) { // Clear errno to allow strerror on error. errno = 0; // Allow access to inital pages that will hold mspace. mprotect(begin, morecoreStart, PROT_READ | PROT_WRITE); // Create mspace using our backing storage starting at begin and with a footprint of // morecoreStart. Don't use an internal dlmalloc lock. When morecoreStart bytes of memory are // exhausted morecore will be called. mspace msp = create_mspace_with_base(begin, morecoreStart, false /*locked*/); if (msp != NULL) { // Do not allow morecore requests to succeed beyond the starting size of the heap. mspace_set_footprint_limit(msp, startingSize); } else { ALOGE("create_mspace_with_base failed %s", strerror(errno)); } return msp; } ~~~ 這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。 參數begin指向前面創建的一塊匿名共享內存的起始地址,也就是Java堆的起始地址,函數createMspace通過C庫提供的函數create_mspace_with_base將該塊匿名共享內存封裝成一個mspace,并且通過調用C庫提供的函數mspace_set_footprint_limit設置該mspace的大小為Java堆的起始大小。 函數addInitialHeap的實現如下所示: ~~~ 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; } ~~~ 這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。 在分析函數addInitialHeap的實現之前,我們先解釋一下兩個數據結構HeapSource和Heap。 在結構體HeapSource中,有一個類型為Heap的數組heaps,如下所示: ~~~ struct HeapSource { ...... /* The heaps; heaps[0] is always the active heap, * which new objects should be allocated from. */ Heap heaps[HEAP_SOURCE_MAX_HEAP_COUNT]; /* The current number of heaps. */ size_t numHeaps; ...... }; ~~~ 這個結構體定義在文件dalvik/vm/alloc/HeapSource.cpp中。 這個Heap數組最多有HEAP_SOURCE_MAX_HEAP_COUNT個Heap,并且當前擁有的Heap個數記錄在numpHeaps中。 HEAP_SOURCE_MAX_HEAP_COUNT是一個宏,定義為2,如下所示: ~~~ /* The largest number of separate heaps we can handle. */ #define HEAP_SOURCE_MAX_HEAP_COUNT 2 這個宏定義在文件dalvik/vm/alloc/HeapSource.h中。 這意味著Dalvik虛擬機的Java堆最多可以劃分為兩個Heap,就是圖1所示的Active堆和Zygote堆。 結構Heap的定義如下所示: [cpp] view plain copy struct Heap { /* The mspace to allocate from. */ mspace msp; /* The largest size that this heap is allowed to grow to. */ size_t maximumSize; /* Number of bytes allocated from this mspace for objects, * including any overhead. This value is NOT exact, and * should only be used as an input for certain heuristics. */ size_t bytesAllocated; /* Number of bytes allocated from this mspace at which a * concurrent garbage collection will be started. */ size_t concurrentStartBytes; /* Number of objects currently allocated from this mspace. */ size_t objectsAllocated; /* * The lowest address of this heap, inclusive. */ char *base; /* * The highest address of this heap, exclusive. */ char *limit; /* * If the heap has an mspace, the current high water mark in * allocations requested via dvmHeapSourceMorecore. */ char *brk; }; ~~~ 這個結構體定義在文件dalvik/vm/alloc/HeapSource.cpp中。 結構體Heap用來描述一個堆,它的各個成員變量的含義如下所示: * msp:描述堆所使用內存塊。 * maximumSize:描述堆可以使用的最大內存值。 * bytesAllocated:描述堆已經分配的字節數。 * concurrentStartBytes:描述堆已經分配的內存達到指定值就要觸發并行GC。 * objectsAllocated:描述已經分配的對象數。 * base:描述堆所使用的內存塊的起始地址。 * limit:描述堆所使用的內存塊的結束地址。 * brk:描述當前堆所分配的最大內存值。 回到函數addInitialHeap中,參數hs和msp指向的是在函數dvmHeapSourceStartup中創建的HeapSource結構體和mspace內存對象,而參數maximumSize描述的Java堆的增長上限值。 通過函數addInitialHeap的實現就可以看出,Dalvik虛擬機在啟動的時候,實際上只創建了一個Heap。這個Heap就是我們在圖1中所說的Active堆,它開始的時候管理的是整個Java堆。但是在圖1中,我們說Java堆實際上還包含有一個Zygote堆的,那么這個Zygote堆是怎么來的呢? 從前面Dalvik虛擬機進程和線程的創建過程分析一文可以知道,Zygote進程會通過調用函數forkAndSpecializeCommon來fork子進程,其中與Dalvik虛擬機Java堆相關的邏輯如下所示: ~~~ static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer) { pid_t pid; ...... if (!dvmGcPreZygoteFork()) { ...... } ...... pid = fork(); if (pid == 0) { ...... } else { ...... } return pid; } ~~~ 這個函數定義在文件dalvik/vm/native/dalvik_system_Zygote.cpp中。 從這里就可以看出,Zygote進程在fork子進程之前,會調用函數dvmGcPreZygoteFork來處理一下Dalvik虛擬機Java堆。接下來我們就看看函數dvmGcPreZygoteFork都做了哪些事情。 函數dvmGcPreZygoteFork的實現如下所示: ~~~ bool dvmGcPreZygoteFork() { return dvmHeapSourceStartupBeforeFork(); } ~~~ 這個函數定義在文件dalvik/vm/alloc/Alloc.cpp中。 函數dvmGcPreZygoteFork只是簡單地封裝了對另外一個函數dvmHeapSourceStartupBeforeFork的調用,后者的實現如下所示: ~~~ bool dvmHeapSourceStartupBeforeFork() { HeapSource *hs = gHs; // use a local to avoid the implicit "volatile" HS_BOILERPLATE(); assert(gDvm.zygote); if (!gDvm.newZygoteHeapAllocated) { /* Ensure heaps are trimmed to minimize footprint pre-fork. */ trimHeaps(); /* Create a new heap for post-fork zygote allocations. We only * try once, even if it fails. */ ALOGV("Splitting out new zygote heap"); gDvm.newZygoteHeapAllocated = true; return addNewHeap(hs); } return true; } ~~~ 這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。 前面我們在分析函數dvmHeapSourceStartup的實現時提到,全局變量gHs指向的是一個HeapSource結構體,它描述了Dalvik虛擬機Java堆的信息。同時,gDvm也是一個全局變量,它的類型為DvmGlobals。gDvm指向的DvmGlobals結構體的成員變量newZygoteHeapAllocated的值被初始化為false。因此,當函數dvmHeapSourceStartupBeforeFork第一次被調用時,它會先調用函數trimHeaps來將Java堆中沒有使用到的內存歸還給系統,接著再調用函數addNewHeap來創建一個新的Heap。這個新的Heap就是圖1所說的Zygote堆了。 由于函數dvmHeapSourceStartupBeforeFork第一次被調用之后,gDvm指向的DvmGlobals結構體的成員變量newZygoteHeapAllocated的值就會被修改為true,因此起到的效果就是以后Zygote進程對函數dvmHeapSourceStartupBeforeFork的調用都是無用功。這也意味著Zygote進程只會在fork第一個子進程的時候,才會將Java堆劃一分為二來管理。 接下來我們就繼續分析函數trimHeaps和addNewHeap的實現,以便更好地理解Dalvik虛擬機是如何管理Java堆的。 函數trimHeaps的實現如下所示: ~~~ /* * Return unused memory to the system if possible. */ static void trimHeaps() { HS_BOILERPLATE(); HeapSource *hs = gHs; size_t heapBytes = 0; for (size_t i = 0; i < hs->numHeaps; i++) { Heap *heap = &hs->heaps[i]; /* Return the wilderness chunk to the system. */ mspace_trim(heap->msp, 0); /* Return any whole free pages to the system. */ mspace_inspect_all(heap->msp, releasePagesInRange, &heapBytes); } /* Same for the native heap. */ dlmalloc_trim(0); size_t nativeBytes = 0; dlmalloc_inspect_all(releasePagesInRange, &nativeBytes); LOGD_HEAP("madvised %zd (GC) + %zd (native) = %zd total bytes", heapBytes, nativeBytes, heapBytes + nativeBytes); } ~~~ 這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。 函數trimHeaps對Dalvik虛擬機使用的Java堆和默認Native堆做了同樣的兩件事情。 第一件事情是調用C庫提供的函數mspace_trim/dlmalloc_trim來將沒有使用到的虛擬內存和物理內存歸還給系統,這是通過系統調用mremap來實現的。 第二件事情是調用C庫提供的函數mspace_inspect_all/dlmalloc_inspect_all將不能使用的內存碎片對應的物理內存歸還給系統,這是通過系統調用madvise來實現的。注意,在此種情況下,只能歸還無用的物理內存,而不能歸還無用的虛擬內存。因為歸還內存碎片對應的虛擬內存會使得堆的整體虛擬地址不連續。 函數addNewHeap的實現如下所示: ~~~ static bool addNewHeap(HeapSource *hs) { Heap heap; assert(hs != NULL); if (hs->numHeaps >= HEAP_SOURCE_MAX_HEAP_COUNT) { ...... return false; } memset(&heap, 0, sizeof(heap)); /* * Heap storage comes from a common virtual memory reservation. * The new heap will start on the page after the old heap. */ char *base = hs->heaps[0].brk; size_t overhead = base - hs->heaps[0].base; assert(((size_t)hs->heaps[0].base & (SYSTEM_PAGE_SIZE - 1)) == 0); if (overhead + hs->minFree >= hs->maximumSize) { ...... return false; } size_t morecoreStart = SYSTEM_PAGE_SIZE; heap.maximumSize = hs->growthLimit - overhead; heap.concurrentStartBytes = hs->minFree - CONCURRENT_START; heap.base = base; heap.limit = heap.base + heap.maximumSize; heap.brk = heap.base + morecoreStart; if (!remapNewHeap(hs, &heap)) { return false; } heap.msp = createMspace(base, morecoreStart, hs->minFree); if (heap.msp == NULL) { return false; } /* Don't let the soon-to-be-old heap grow any further. */ hs->heaps[0].maximumSize = overhead; hs->heaps[0].limit = base; mspace_set_footprint_limit(hs->heaps[0].msp, overhead); /* Put the new heap in the list, at heaps[0]. * Shift existing heaps down. */ memmove(&hs->heaps[1], &hs->heaps[0], hs->numHeaps * sizeof(hs->heaps[0])); hs->heaps[0] = heap; hs->numHeaps++; return true; } ~~~ 這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。 函數addNewHeap所做的事情實際上就是將前面創建的Dalvik虛擬機Java堆一分為二,得到兩個Heap。 在劃分之前,HeadSource結構體hs只有一個Heap,如圖2所示: ![](https://box.kancloud.cn/bc401b83319abbc9e248771e8f73f60e_500x117.png) 圖2 Dalvik虛擬機Java堆一分為二之前 接下來在未使用的Dalvik虛擬機Java堆中創建另外一個Heap,如圖3所示: ![](https://box.kancloud.cn/fe3816597392cd781be1c71853b37e33_513x159.png) 圖3 在未使用的Dalvik虛擬機Java堆中創建一個新的Heap 最后調整HeadSource結構體hs的heaps數組,即交heaps[0]和heaps[1]的值,結果如圖4所示: ![](https://box.kancloud.cn/2fb15d173033e7361be3ec239a850dae_557x166.png) 圖4 Dalvik虛擬機Java堆一分為二之后 其中,heaps[1]就是我們在圖1中所說的Zygote堆,而heaps[0]就是我們在圖1中所說的Active堆。以后無論是Zygote進程,還是Zygote子進程,需要分配對象時,都在Active堆上進行。這樣就可以使得Zygote堆最大限度地在Zygote進程及其子進程中共享。 這樣我們就分析完了函數addInitialHeap及其相關函數的實現,接下來我們繼續分析函數dvmHeapBitmapInit和allocMarkStack的實現。 函數dvmHeapBitmapInit的實現如下所示: ~~~ 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; } ~~~ 這個函數定義在文件dalvik/vm/alloc/HeapBitmap.cpp中。 參數hb指向一個HeapBitmap結構體,這個結構體正是函數dvmHeapBitmapInit要進行初始化的。參數base和maxSize描述的是Java堆的起始地址和大小。另外一個參數name描述的是參數hb指向的HeapBitmap結構體的名稱。 在分析函數dvmHeapBitmapInit的實現之前,我們先來了解一下結構體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; }; ~~~ 這個結構體定義在文件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的實現就一目了然了。 接下來我們再來看函數allocMarkStack的實現,如下所示: ~~~ 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; } ~~~ 這個函數定義在文件vm/alloc/HeapSource.cpp中。 參數stack指向的是一個GcMarkStack結構體,這個結構體正是函數allocMarkStack要進行初始化的。參數maximumSize描述的是Java堆的大小。 同樣是在分析函數allocMarkStack的實現之前,我們先來了解一下結構體GcMarkStack的定義,如下所示: ~~~ 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; }; ~~~ 這個結構體定義在文件dalvik/vm/alloc/MarkSweep.h中。 代碼對HeapBitmap結構體的各個成員變量的含義已經有很詳細的注釋。總結來說,GcMarkStack通過一個Object*數組來描述一個棧。這個Object*數組的大小通過成員變量length來描述。成員變量base和limit分別描述棧的最低地址和最高地址,另外一個成員變量top指向棧頂。 回到函數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棧。 有了上述的基礎知識之后,函數allocMarkStack的實現同樣也一目了然了。 這樣,函數dvmHeapSourceStartup及其相關的函數dvmAllocRegion、createMspace、addInitialHeap、dvmHeapBitmapInit和allockMarkStack的實現我們就分析完了,回到前面的函數dvmHeapStartup中,它調用函數dvmHeapSourceStartup創建完成Java堆及其相關的Heap Bitmap和Mark Stack之后,還需要繼續調用函數dvmCardTableStartup來創建一個Card Table。這個Card Table在執行Concurrent GC時要使用到。 函數dvmCardTableStartup的實現如下所示: ~~~ /* * Maintain a card table from the the write barrier. All writes of * non-NULL values to heap addresses should go through an entry in * WriteBarrier, and from there to here. * * The heap is divided into "cards" of GC_CARD_SIZE bytes, as * determined by GC_CARD_SHIFT. The card table contains one byte of * data per card, to be used by the GC. The value of the byte will be * one of GC_CARD_CLEAN or GC_CARD_DIRTY. * * After any store of a non-NULL object pointer into a heap object, * code is obliged to mark the card dirty. The setters in * ObjectInlines.h [such as dvmSetFieldObject] do this for you. The * JIT and fast interpreters also contain code to mark cards as dirty. * * The card table's base [the "biased card table"] gets set to a * rather strange value. In order to keep the JIT from having to * fabricate or load GC_DIRTY_CARD to store into the card table, * biased base is within the mmap allocation at a point where it's low * byte is equal to GC_DIRTY_CARD. See dvmCardTableStartup for details. */ /* * Initializes the card table; must be called before any other * dvmCardTable*() functions. */ bool dvmCardTableStartup(size_t heapMaximumSize, size_t growthLimit) { size_t length; void *allocBase; u1 *biasedBase; GcHeap *gcHeap = gDvm.gcHeap; int offset; void *heapBase = dvmHeapSourceGetBase(); assert(gcHeap != NULL); assert(heapBase != NULL); /* All zeros is the correct initial value; all clean. */ assert(GC_CARD_CLEAN == 0); /* Set up the card table */ length = heapMaximumSize / GC_CARD_SIZE; /* Allocate an extra 256 bytes to allow fixed low-byte of base */ allocBase = dvmAllocRegion(length + 0x100, PROT_READ | PROT_WRITE, "dalvik-card-table"); if (allocBase == NULL) { return false; } gcHeap->cardTableBase = (u1*)allocBase; gcHeap->cardTableLength = growthLimit / GC_CARD_SIZE; gcHeap->cardTableMaxLength = length; biasedBase = (u1 *)((uintptr_t)allocBase - ((uintptr_t)heapBase >> GC_CARD_SHIFT)); offset = GC_CARD_DIRTY - ((uintptr_t)biasedBase & 0xff); gcHeap->cardTableOffset = offset + (offset < 0 ? 0x100 : 0); biasedBase += gcHeap->cardTableOffset; assert(((uintptr_t)biasedBase & 0xff) == GC_CARD_DIRTY); gDvm.biasedCardTableBase = biasedBase; return true; } ~~~ 這個函數定主在文件dalvik/vm/alloc/CardTable.cpp中。 參數heapMaximumSize和growthLimit描述的是Java堆的最大值和增長上限值。 在Dalvik虛擬機中,Card Table和Heap Bitmap的作用是類似的。區別在于: 1. Card Table不是使用一個bit來描述一個對象,而是用一個byte來描述GC_CARD_SIZE個對象; 2. Card Table不是用來描述對象的存活,而是用來描述在Concurrent GC的過程中被修改的對象,這些對象需要進行特殊處理。 全局變量gDvm的成員變量gcHeap指向了一個GcHeap結構體。在GcHeap結構體,通過cardTableBase、cardTableLength、cardTableMaxLength和cardTableOffset這四個成員變量來描述一個Card Table。它們的定義如下所示: ~~~ struct GcHeap { ...... /* GC's card table */ u1* cardTableBase; size_t cardTableLength; size_t cardTableMaxLength; size_t cardTableOffset; ...... }; ~~~ 這個結構體定義在文件dalvik/vm/alloc/HeapInternal.h中。 其中,成員變量cardTableBase和cardTableMaxLength描述的是創建的Card Table和起始地址和大小。成員變量cardTableLength描述的當前Card Table使用的大小。成員變量cardTableMaxLength和cardTableLength的關系就對應于Java堆的最大值(Maximum Size)和增長上限值(Growth Limit)的關系。 Card Table在真正使用的時候,并不是從成員變量cardTableBase描述的起始地址開始的,而是從一個相對起始地址有一定偏移的位置開始的。這個偏移量記錄在成員變量cardTableOffset中。相應地,Java堆的起始地址和Card Table的偏移地址的差值記錄在全局變量gDvm指向的結構體DvmGlobals的成員變量biasedCardTableBase。按照函數dvmCardTableStartup前面的注釋,之所以要這樣做,是為了避免JIT在Card Table偽造假值。至于JIT會在Card Table偽造假值的原因,就不得而知,因為還沒有研究JIT。在此也希望了解的同學可以告訴一下老羅:) 前面我們提到,在Card Table中,用一個byte來描述GC_CARD_SIZE個對象。GC_CARD_SIZE是一個宏,它的定義如下所示: ~~~ #define GC_CARD_SHIFT 7 #define GC_CARD_SIZE (1 << GC_CARD_SHIFT) ~~~ 這兩個宏定義在文件dalvik/vm/alloc/CardTable.h中。 也就是說,在Card Table中,用一個byte來描述128個對象。每當一個對象在Concurrent GC的過程中被修改時,典型的情景就是我們通過函數dvmSetFieldObje修改了該對象的引用類型的成員變量。在這種情況下,該對象在Card Table中對應的字節會被設置為GC_CARD_DIRTY。相反,如果一個對象在Concurrent GC的過程中沒有被修改,那么它在Card Table中對應的字節會保持為GC_CARD_CLEAN。 GC_CARD_DIRTY和GC_CARD_CLEAN是兩個宏,它們的定義在如下所示: ~~~ #define GC_CARD_CLEAN 0 #define GC_CARD_DIRTY 0x70 ~~~ 這兩個宏定義在文件dalvik/vm/alloc/CardTable.h中。 接下來我們再通過四個函數dvmIsValidCard、dvmCardFromAddr、dvmAddrFromCard和dvmMarkCard來進一步理解Card Table和對象地址的關系。 函數dvmIsValidCard的實現如下所示: ~~~ /* * Returns true iff the address is within the bounds of the card table. */ bool dvmIsValidCard(const u1 *cardAddr) { GcHeap *h = gDvm.gcHeap; u1* begin = h->cardTableBase + h->cardTableOffset; u1* end = &begin[h->cardTableLength]; return cardAddr >= begin && cardAddr < end; } ~~~ 這個函數定義在文件dalvik/vm/alloc/CardTable.cpp中。 參數cardAddr描述的是一個Card Table內部的地址。由于上述的偏移地址的存在,并不是所有的Card Table內部地址都是正確的Card Table地址。只有大于等于偏移地址并且小于當前使用的地址的地址才是正確的地址。 Card Table的起始地址記錄在GcHeap結構體的成員變量cardTableBase中,而偏移量記錄在另外一個成員變量cardTableOffset中,因此將這兩個值相加即可得到Card Table的偏移地址。另外,當前Card Table使用的大小記錄在GcHeap結構體的成員變量cardTableLength中,因此,通過這些信息我們就可以判斷參數cardAddr描述的是否是一個正確的Card Table地址。 函數dvmCardFromAddr的實現如下所示: ~~~ /* * Returns the address of the relevant byte in the card table, given * an address on the heap. */ u1 *dvmCardFromAddr(const void *addr) { u1 *biasedBase = gDvm.biasedCardTableBase; u1 *cardAddr = biasedBase + ((uintptr_t)addr >> GC_CARD_SHIFT); assert(dvmIsValidCard(cardAddr)); return cardAddr; } ~~~ 這個函數定義在文件dalvik/vm/alloc/CardTable.cpp中。 參數addr描述的是一個對象地址,函數dvmCardFromAddr返回它在Card Table中對應的字節的地址。全局變量gDvm指向的結構體DvmGlobals的成員變量biasedCardTableBase記錄的是Java堆的起始地址與Card Table的偏移地址的差值。將參數addr的值左移GC_CARD_SHIFT位,相當于是得到對象addr在Card Table的字節索引值。將這個索引值加上Java堆的起始地址與Card Table的偏移地址的差值,即可得到對象addr在Card Table中對應的字節的地址。 函數dvmAddrFromCard的實現如下所示: ~~~ /* * Returns the first address in the heap which maps to this card. */ void *dvmAddrFromCard(const u1 *cardAddr) { assert(dvmIsValidCard(cardAddr)); uintptr_t offset = cardAddr - gDvm.biasedCardTableBase; return (void *)(offset << GC_CARD_SHIFT); } ~~~ 這個函數定義在文件dalvik/vm/alloc/CardTable.cpp中。 參數cardAddr描述的是一個Card Table地址,函數dvmAddrFromCard返回它對應的對象的地址,它所執行的操作剛剛是和上面分析的函數dvmCardFromAddr相反。在此這里不再多述,同學會自己體會一下。 函數dvmMarkCard的實現如下所示: ~~~ /* * Dirties the card for the given address. */ void dvmMarkCard(const void *addr) { u1 *cardAddr = dvmCardFromAddr(addr); *cardAddr = GC_CARD_DIRTY; } ~~~ 這個函數定義在文件dalvik/vm/alloc/CardTable.cpp中。 在Concurrent GC執行的過程中,如果修改了一個對象的類型為引用的成員變量,那么就需要調用函數dvmMarkCard來將該對象在Card Table中對應的字節設置為GC_CARD_DIRTY,以便后面可以對這個對象進行特殊的處理。這個特殊的處理我們后面分析Dalvik虛擬機的垃圾收集過程時再分析。 函數dvmMarkCard的實現很簡單,它首先是通過函數dvmCardFromAddr找到對象在Card Table中對應的字節的地址,然后再將訪字節的值設置為GC_CARD_DIRTY。 有了這些基礎知識之后,回到函數dvmCardTableStartup中,我們需要知道要創建的Card Table的大小以及該Card Table使用的偏移量。正常來說,我們需要的Card Table的大小為(heapMaximumSize / GC_CARD_SIZE),其中,heapMaximumSize為Java堆的大小。但是前面分析的偏移量的存在,我們需要額外的一些內存。額外的內存大小為0x100,即256個字節。因此,我們最終需要的Card Table的大小length就為: ~~~ (heapMaximumSize / GC_CARD_SIZE) + 0x100 ~~~ 我們通過調用函數dvmAllocRegion來創建Card Table,得到其起始地址為allocBase。接下來就可以計算Card Table使用的偏移地址。 首先是計算一個偏移地址biasedBase: ~~~ (u1 *)((uintptr_t)allocBase - ((uintptr_t)heapBase >> GC_CARD_SHIFT)) ~~~ 其中,heapBase是Java堆的起始地址。 用GC_CARD_DIRTY的值減去biasedBase地址的低8位,就可以得到一個初始偏移量offset: GC_CARD_DIRTY - ((uintptr_t)biasedBase & 0xff) GC_CARD_DIRTY的值定義為0x70,biasedBase地址的低8位描述的值界于0和0xff之間,因此,上面計算得到的offset可能為負數。在這種情況下,需要將它的值加上256,這是因為我們需要保證Card Table使用的偏移量是正數。最終得到的偏移量如下所示: ~~~ offset + (offset < 0 ? 0x100 : 0) ~~~ 這里之所以是加上256,是因為我們在創建Card Table的時候,額外地增加了256個字節,因此這里不僅可以保證偏移量是正數,還可以保證最終使用的Card Table不會超出前面通過調用函數dvmAllocRegion創建的內存塊范圍。 上述計算得到的偏移量保存在gcHeap->cardTableOffset中。相應地,Java堆的起始地址和Card Table使用的偏移地址的差值需要調整為: ~~~ biasedBase + gcHeap->cardTableOffset ~~~ 得到的結果保存在gDvm.biasedCardTableBase中。 這里之所以要采取這么奇怪的算法來給Card Table設置一個偏移量,就是為了前面說的,避免JIT在Card Table偽造假值。 至此,我們就分析完成Dalvik虛擬機在啟動的過程中創建Java堆及其相關的Mark Heap Bitmap、Live Heap Bitmap、Mark Stack和Card Table數據結構了。有了這些基礎知識,接下來我們就可以繼續分析Java對象的分配過程和垃圾收集過程了,
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看