还剩22页未读,继续阅读
本资源只提供10页预览,全部文档请下载后查看!喜欢就下载吧,查找使用更方便
文本内容:
Dalvik虚拟机Java堆创建过程分析使用C/C++开发应用程序最令头痛的问题就是内存管理慎不留神,要么内存泄漏,要么内存破坏虚拟机要解决的问题之一就是帮助应用程序自动分配和释放内存为了达到这个目的,虚拟机在启动的时候向操作系统申请一大块内存当作对象堆之后当应用程序创建对象时,虚拟机就会在堆上分配合适的内存块而当对象不再使用时,虚拟机就会将它占用的内存块归还给堆Dalvik虚拟机也不例外,本文就分析它的Java堆创建过程从前面一文可以知道,在Dalvik虚拟机中,Java堆实际上是由一个Active堆和一个Zygote堆组成的,如图1所示HeapSourceActMHeapZygoteHeapCardTable图1Dalvi嘘拟机的Java堆其中,Zygote堆用来管理Zygote进程在启动过程中预加载和创建的各种对象,而Active堆是在Zygote进程fork第一个子进程之前创建的之后无论是Zygote进程还是其子进程,都在Active堆上进行对象分配和释放这样做的目的是使得Zygote进程和其子进程最大限度地共享Zygote堆所占用的内存为了管理Java堆,Dalvik虚拟机需要一些辅助数据结构,包括一个CardTable、两个HeapBitm叩和一个MarkStackoCardTable是为了记录在垃圾收集过程中对象的引用情况的,以便可以实现ConcurrentG图1的两个HeapBitmap一个称为LiveHeapBitmap用来记录上次GC之后,还存活的对象,另一个称为MarkHeapBitmap用来记录当前GC中还存活的对象这样,上次GC后存活的但是当前GC不存活的对象,就是需要释放的对象Davlk虚拟机使用标记-清除Mark-Sweep算法进行GC在标记阶段,通过一个MarkStack来实现递归检查被引用的对象,即在当前GC中存活的对象有了这个MarkStack就可以通过循环来模拟函数递归调用Dalvik虚拟机Java堆的创建过程实际上就是上面分析的各种数据结构的创建过程,它们是在Dalvik虚拟机启动的过程中创建的接下来,我们就详细分析这个过程从前面一文可以知道Dalvik虚拟机在启动的过程中,会通过调用函数dvmGcStartup来创建Java堆,它的实现如下所示limit描述堆所使用的内存块的结束地址brk描述当前堆所分配的最大内存值回到函数addlnitialHeap中,参数hs和msp指向的是在函数dvmHeapSourceStartup中创建的HeapSource结构体和mspace内存对象,而参数maximumSize描述的Java堆的增长上限值通过函数addlnitialHeap的实现就可以看出,Dalvik虚拟机在启动的时候,实际上只创建了一个Heapo这个Heap就是我们在图1中所说的Active堆,它开始的时候管理的是整个Java堆但是在图1中,我们说Java堆实际上还包含有一个Zygote堆的,那么这个Zygote堆是怎么来的呢?从前面一文可以知道,Zygote进程会通过调用函数forkAndSpecializeCommon来fork子进程,其中与Dalvik虚拟机Java堆相关的逻辑如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片staticpid_tforkAndSpecializeCommonconstu4*argsboolisSystemServerpid_tpid;if!dvmGcPreZygoteFork{pid=fork;ifpid==0{}else{returnpid;这个函数定义在文件dalvik/vm/nativc/dalvik_systcm_Zygotc.cpp中从这里就可以看出,Zygote进程在fork子进程之前,会调用函数dvmGcPreZygoteFork来处理一下Dalvik虚拟机Java堆接下来我们就看看函数dvmGcPreZygoteFork都做了哪些事情函数dvmGcPreZygoteFork的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片booldvmGcPreZygoteForkreturndvmHeapSourceStartupBeforeFork;这个函数定义在文件dalvik/vm/alloc/Alloc.cpp中函数dvmGcPreZygoteFork只是简单地封装了对另外一个函数dvmHeapSourceStartupBeforeFork的调用,后者的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片booldvmHeapSourceStartupBeforeForkHeapSource*hs=gHs;//usealocaltoavoidtheimplicitvolatile”HS_BOILERPLATE;assertgDvm.zygote;ifIgDvm.newZygoteHeapAllocated{/*Ensureheapsaretrimmedtominimizefootprintpre-fork.*/trimHeaps;/*Createanewheapforpost-forkzygoteallocations.Weonly*tryonceevenifitfails.*/ALOGVnSplittingoutnewzygoteheap;gDvm.newZygoteHeapAllocated=true;returnaddNewHeaphs;returntrue;这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中前面我们在分析函数dvmHeapSourceStartup的实现时提到,全局变量gHs指向的是一个HeapSource结构体,它描述了Dalvik虚拟机Java堆的信息同时,gDvm也是一个全局变量,它的类型为DvmGlobalsogDvm指向的DvmGlobals结构体的成员变量newZygoteHeapAllocated的值被初始化为false因此,当函数dvmHeapSourceStartupBeforeFork第一次被调用时,它会先调用函数trimHeaps来将Java堆中没有使用到的内存归还给系统,接着再调用函数addNcwHcap来创建一个新的Heap这个新的Heap就是图1所说的Zygote堆了由于函数dvmHeapSourceStartupBeforeFork第一次被调用之后,gDvm指向的DvmGlobals结构体的成员变量newZygoteHeapAllocated的值就会被修改为true因此起到的效果就是以后Zygote进程对函数dvmHeapSourceStartupBeforeFork的调用都是无用功这也意味着Zygote进程只会在fork第一个子进程的时候,才会将Java堆划一分为二来管理接下来我们就继续分析函数trimHeaps和addNewHeap的实现,以便更好地理解Dalvik虚拟机是如何管理Java堆的函数trimHeaps的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片/**Returnunusedmemorytothesystemifpossible.*/staticvoidtrimHeapsHS_BOILERPLATE;HeapSource*hs=gHs;size_theapBytes=0;forsize_ti=0;ihs-numHeaps;i++{Heap*heap=hs-heaps[i];/*Returnthewildernesschunktothesystem.*/mspace_trimheap-msp0;/*Returnanywholefreepagestothesystem.*/mspace_inspect_allheap-mspreleasePagesInRangeheapBytes;/*Sameforthenativeheap.*/dlmalloc_trim0;size_tnativeBytes=0;dlmalloc_inspect_allreleasePagesInRangenativeBytes;LOGD_HEAPnmadvised%zdGC+%zdnative=%zdtotalbytes”,heapBytesnativeBytesheapBytes+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的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片staticbooladdNewHeapHeapSource*hsHeapheap;asserths!=NULL;ifhs-numHeaps=HEAP_SOURCE_MAX_HEAP_COUNT{returnfalse;memsetheap0sizeofheap;/**Heapstoragecomesfromacommonvirtualmemoryreservation.*Thenewheapwillstartonthepageaftertheoldheap.*/char*base=hs-heaps|O].brk;size_toverhead=base-hs-heaps[O].base;assertsize_ths-heaps[O].baseSYSTEM_PAGE_SIZE-1==0;ifoverhead+hs-minFree=hs-maximumSize{returnfalse;size_tmorecoreStart=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!remapNewHeaphsheap{returnfalse;heap.msp=createMspacebasemorecoreStarths-minFree;ifheap.msp==NULL{returnfalse;/*Don!tletthesoon-to-be-oldheapgrowanyfurther.*/hs-heaps
[0].maximumSize=overhead;hs-heaps
[0].limit二base;mspace_set_footprint_limiths-heaps
[0].mspoverhead;/*Putthenewheapinthelistatheaps
[0].*Shiftexistingheapsdown.*/memmovehs-heaps
[1]hs-heaps
[0]hs-numHeaps*sizeofhs-heaps
[0];hs-heaps
[0]=heap;hs-numHeaps++;returntrue;这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中函数addNewHeap所做的事情实际上就是将前面创建的Dalvik虚拟机Java堆一分为二,得到两个Heap在划分之前,HeadSource结构体hs只有一个Heap如图2所示overheadh$-heaps|O|.basehs*hedps{O|.bckhs-heapS[O].Wnit图2DaM嘘拟机Java堆一分为二之前接下来在未使用的Dalvi嘘拟机Java堆中创建另外一个Heap如图3所示hs*heaps[O].lifnit■--overheadhs*heap5[O].basehs**heaps
[0].hp^n.hrirheap.linMt图3在未使用的Dalvi嘘拟机Java堆中创建一个新的Heap最后调整HeadSource结构体hs的heaps数蛆j即交heaps]和heaps
[1]的值结果如图4所示hs-beaps[l].bmiths-heaps[O].base♦.
74.卜overheadhs-heaps[l].basehs-beaps[l].bdchs-heaps[O].brkhs-heapslOj.hrrwt图4Dalvi嘘拟机Java堆一分为二之后其中,heaps[l]就是我们在图1中所说的Zygote堆,而heaps[O]就是我们在图1中所说的Active堆以后无论是Zygote进程,还是Zygote子进程,需要分配对象时,都在Active堆上进行这样就可以使得Zygote堆最大限度地在Zygote进程及其子进程中共享这样我们就分析完了函数addlnitialHeap及其相关函数的实现,接下来我们继续分析函数dvmHeapBitmapInit和allocMarkStack的实现函数dvmHeapBitmapInit的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片booldvmHeapBitmapInitHeapBitmap*hbconstvoid*basesize_tmaxSizeconstchar*namevoid*bits;sizetbitsLen;asserthb!=NULL;assertname!=NULL;bitsLen=HB_OFFSET_TO」NDEXmaxSize*sizeof*hb-bits;bits=dvmAllocRegionbitsLenPROT_READ|PROT_WRITEname;ifbits==NULL{ALOGEnCouldnotmmap%zd-byteashmemregion%sHbitsLenname;returnfalse;hb-bits=unsignedlong*bits;hb-bitsLen=hb-allocLen=bitsLen;hb-base=uintptr_tbase;hb-max=hb-base-1;returntrue;这个函数定义在文件dalvik/vm/alloc/HeapBitmap.cpp中参数hb指向一个HeapBitmap结构体,这个结构体正是函数dvmHeapBitm叩Init要进行初始化的参数base和maxSize描述的是Java堆的起始地址和大小另夕I、一个参数name描述的是参数hb指向的HeapBitmap结构体的名称在分析函数dvmHeapBitmapInit的实现之前,我们先来了解一下结构体HeapBitmap的定义,如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片structHeapBitwww.shanxiwang.netmap{/*Thebitmapdatawhichpointstoanmmapedareaofzeroed*anonymousmemory.*/unsignedlong*bits;/*Thesizeoftheusedmemorypointedtobybitsinbytes.This*valuechangeswhenthebitmapisshrunk.*/size_tbitsLen;/*Therealsizeofthememorypointedtobybits.Thisisthenumberofbyteswerequestedfromtheallocatoranddoesnotchange./size_tallocLen;/*Thebaseaddresswhichcorrespondstothefirstbitinthebitmap./uintptr_tbase;/*Thehighestpointervalueeverreturnedbyanallocationfromthisheap.I.e.thehighestaddressthatmaycorrespondtoasetbit.Iftherearenobitssetmaxbase./uintptr_tmax;;这个结构体定义在文件dalvik/vm/alloc/HeapBitmap.ho代码对HeapBitmap结构体的各个成员变量的含义已经有很详细的注释,其中最重要的就是成员变量bits指向的一个类型为unsignedlong的数组,这个数组的每一个bit都用来标记一个对象是否存活回到函数dvmHeapBitmapInit中,Java堆的起始地址为base大小为maxSize由此我们就知道,在Java堆上创建的对象的地址范围为[basemaxSizeo但是通过C库提供的mspace_malloc来在Java堆分配内存时,得到的内存地址是以8字节对齐的这意味着我们只需要maxSize/8个bit来描述Java堆的对象结构体HeapBitmap的成员变量bits是一个类型为unsignedlong的数组也就是说数组中的每一个元素都可以描述sizeofunsignedlong个对象的存活在32位设备上,一个unsignedlong占用32个bit这意味着需要一个大小为maxSize/8/32的unsignedlong数组来描述Java堆对象的存活如果换成字节数来描述的话,就是说我们需要一块大小为maxSize/8/32X4的内存块来描述一个大小为maxSize的Java堆对象Dalvik虚拟机提供了一些宏来描述对象地址与HeapBitmap结构体的成员变量bits所描述的unsignedlong数组的关系,如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片#defineHBOBJECTALIGNMENT8#defineHB_BITS_PER_WORDsizeofunsignedlong*CHAR_BIT/*offsetisthedifferencefrom.basetoapointeraddress.indexistheindexof.bitsthatcontainsthebitrepresentingoffset.*/#defineHB_OFFSET_TO_INDEXoffset_\uintptr_toffset_/HB_OBJECT_ALIGNMENT/HB_BITS_PER_WORD#defineHB_INDEX_TO_OFFSETindex_\uintptr_tindex_*HB_OBJECT_ALIGNMENT*HB_BITS_PER_WORD#defineHB_OFFSET_TO_BYTE_INDEXoffset_\HB_OFFSET_TO_INDEXoffset_*sizeof^HeapBitmap*O-bits这些宏定义在文件dalvik/vm/alloc/HeapBitmap.h中假设我们知道了一个对象的地址为ptrJava堆的起始地址为base那么就可以计算得到一个偏移值offset有了这个偏移值之后,就可以通过宏HB_OFFSET_TO」NDEX计算得到用来描述该对象存活的bit位于HeapBitmap结构体的成员变量bits所描述的unsignedlong数组的索引index有了这个index之后,我们就可以得到一个unsignedlong值接着再通过对象地址ptr的第4到第8位表示的数值为索引,在前面找到的unsignedlong值取出相应的位,就可以得到该对象是否存活了相反,给出一个HeapBitmap结构体的成员变量bits所描述的unsignedlong数组的索引index我们可以通过宏HB」NDEX_TO_OFFSET找到一个偏移值offset将这个偏移值加上Java堆的起始地址base就可以得到一个Java对象的地址ptr第三个宏HB_OFFSET_TO_BYTE_INDEX借助宏HB_OFFSET_TO_INDEX来找出用来描述对象存活的bit在HeapBitmap结构体的成员变量bits所描述的内存块的字节索引有了上述的基础知识之后,函数dvmHeapBitmapInit的实现就一目了然了接下来我们再来看函数allocMarkStack的实现,如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片staticboolallocMarkStackGcMarkStackstacksize_tmaximumSizeconstchar*name=ndalvik-mark-stackn;void*addr;asscrtstack!=NULL;stack-length=maximumSize*sizeofObject*/sizeofObject+HEAP_SOURCE_CHUNK_OVERHEAD;addr=dvmAllocRegionstack-lengthPROT_READ|PROT_WRITEname;ifaddr==NULL{returnfalse;stack-base=constObject**addr;stack-limit二constObject**char*addr+stack-length;stack-top=NULL;madvisestack-basestack-lengthMADV_DONTNEED;returntrue;这个函数定义在文件vm/alloc/HeapSource.cpp中参数stack指向的是一个GcMarkStack结构体,这个结构体正是函数allocMarkStack要进行初始化的参数maximumSize描述的是Java堆的大小同样是在分析函数allocMarkStack的实现之前,我们先来了解一下结构体GcMarkStack的定义,如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片structGcMarkStack{/*Highestaddressexclusive刃constObject**limit;/*Currenttopofthestackexclusive*/constObject**top;/*Lowestaddressinclusive*/constObject**base;/*Maximumstacksizeinbytes.*/sizetlength;;这个结构体定义在文件dalvik/vm/alloc/MarkSweep.h中代码对HeapBitmap结构体的各个成员变量的含义已经有很详细的注释总结来说GcMarkStack通过一个Object*数组来描述一个栈这个Object*数组的大小通过成员变量length来描述成员变量base和limit分别描述栈的最低地址和最高地址,另外一个成员变量top指向栈顶回到函数allocMarkStack中,我们分析一下需要一个多大的栈来描述Java堆的所有对象首先,每一个Java对象都是必须要从Object结构体继承下来的,这意味着每一个Java对象占用的内存都至少为sizeofObjecto其次,通过C库提供的接口mspace_malloc在Java堆上为对象分配内存时,C库自己需要一些额外的内存来管理该块内存,例如用额外的4个字节来记录分配出去的内存块的大小额外需要的内存大小通过宏HEAP_SOURCE_CHUNK_OVERHEAD来描述最后,我们就可以知道,一个大小为maximumSize的Java堆,在最坏情况下,存在maximumSize/sizeofObject+HEAP_SOURCE_CHUNK_OVERHEAD个对象也就是说,GcMarkStack通过一个大小为maximumSize/sizeofObject+HEAP_SOURCE_CHUNK_OVERHEAD的Object*数组来描述一个栈如果换成字节数来描述的话,就是说我们需要一块大小为maximumSize*sizeofObject*/sizeofObject+HEAP_SOURCE_CHUNK_OVERHEAD的内存块来描述——个GcMarkStack栈有了上述的基础知识之后,函数allocMarkStack的实现同样也一目了然了这样,函数dvmHeapSourceStartup及其相关的函数dvmAllocRegioncreateMspaceaddInitialHeapdvmHeapBitmapInit和allockMarkStack的实现我们就分析完了,回到前面的函数dvmHeapStartup中,它调用函数dvmHeapSourceStartup创建完成Java堆及其相关的HeapBitmap和MarkStack之后,还需要继续调用函数dvmCardTableStartup来创建一个CardTableo这个CardTable在执行ConcurrentGC时要使用到函数dvmCardTableStartup的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片/*Maintainacardtablefromthethewritebarrier.Allwritesofnon-NULLvaluestoheapaddressesshouldgothroughanentryinWriteBarrierandfromtheretohere.*Theheapisdividedinto-cards”ofGC_CARD_SIZEbytesasdeterminedbyGC_CARD_SHIFT.ThecardtablecontainsonebyteofdatapercardtobeusedbytheGC.ThevalueofthebytewillbeoneofGC_CARD_CLEANorGC_CARD_DIRTY.*Afteranystoreofanon-NULLobjectpointerintoaheapobjectcodeisobligedtomarkthecarddirty.ThesettersinObjectlnlines.h[suchasdvmSetFieldObject]dothisforyou.TheJITandfastinterpretersalsocontaincodetomarkcardsasdirty.*Thecardtablesbase|thebiasedcardtable]getssettoaratherstrangevalue.InordertokeeptheJITfromhavingtofabricateorloadGC_DIRTY_CARDtostoreintothecardtablebiasedbaseiswithinthemmapallocationatapointwhereitslowbyteisequaltoGC_DIRTY_CARD.SeedvmCardTableStartupfordetails.*//*Initializesthecardtable;mustbecalledbeforeanyotherdvmCardTable^Ofunctions.*/[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片booldvmGcStartupdvmInitMutexgDvm.gcHeapLock;pthread_cond_initgDvm.gcHeapCondNULL;returndvmHeapStartupO;这个函数定义在文件dalvik/vm/alloc/Alloc.cpp中函数dvmGcStartup首先是分别初始化一个锁和一个条件变量,它们都是用来保护堆的并行访问的,接着再调用另外一个函数dvmHeapStartup来创建Java堆函数dvmHeapStartup的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片booldvmHeapStartupOGcHeap*gcHeap;ifgDvm.heapGrowthLimit==0{gDvm.heapGrowthLimit=gDvm.heapMaximumSize;gcHeap=dvmHeapSourceStartupgDvm.heapStartingSizegDvm.heapMaximumSizegDvm.heapGrowthLimit;gDvm.gcHeap=gcHeap;if!dvmCardTableStartupgDvm.heapMaximumSizegDvm.heapGrowthLimit{LOGE_HEAPncardtablestartupfailed/;returnfalse;returntrue;这个函数定义在文件dalvik/vm/alloc/Hcap.cpp中gDvm是一个类型为DvmGlobals的全局变量,它通过各个成员变量记录了Dalv汰虚拟机的各种信息这里涉及到三个重要与Java堆相关的信息,分别是Java堆的起始大小StartingSize、最大值MaximumSize和增长上限值GrowthLimito在启动Dalvik虚拟机的时候,我们可以分别通过-Xms、-Xmx和-XX:HeapGrowthLimit三个选项来指定上述三个值booldvmCardTableStartupsize_theapMaximumSizesize_tgrowthLimitsize_tlength;void^allocBase;ul*biasedBase;GcHeap*gcHeap=gDvm.gcHeap;intoffset;void*heapBase二dvmHeapSourceGetBase;assertgcHeap!=NULL;assertheapBase!=NULL;/*Allzerosisthecorrectinitialvalue;allclean.*/assertGC_CARD_CLEAN=二0;/*Setupthecardtable*/length=heapMaximumSize/GC_CARD_SIZE;/*Allocateanextra256bytestoallowfixedlow-byteofbase*/allocBase=dvmAllocRegionlength+0x100PROT_READ|PROT_WRITEndalvik-card-tablen;ifallocBase==NULL{returnfalse;gcHeap-cardTableBase=uPallocBase;gcHeap-cardTableLength=growthLimit/GC_CARD_S1ZE;gcHeap-cardTableMaxLength=length;biasedBase=ul^uintptr_tallocBase-uintptr_theapBase»GC_CARD_SHIFT;offset=GC_CARD_DIRTY-{uintptr_tbiasedBaseOxff;gcHeap-cardTableOffset=offset+offset00x100:0;biasedBase+=gcHeap-cardTableOffset;assertuintptr_tbiasedBaseOxff==GC_CARD_DIRTY;gDvm.biasedCardTableBase=biasedBase;returntrue;这个函数定主在文件dalvik/vm/alloc/CardTable.cpp中参数heapMaximumSize和growthLimit描述的是Java堆的最大值和增长上限值在Dalvik虚拟机中,CardTable和HeapBitm叩的作用是类似的区别在于CardTable不是使用一个bit来描述一个对象,而是用一个byte来描述GC_CARD_SIZE个对象;CardTable不是用来描述对象的存活,而是用来描述在ConcurrentGC的过程中被修改的对象,这些对象需要进行特殊处理全局变量gDvm的成员变量gcHeap指向了一个GcHeap结构体在GcHeap结构体通过cardTableBase、cardTableLengthcardTableMaxLength和cardTableOffset这四个成员变量来描述一个CardTable它们的定义如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片structGcHeap{/*GCscardtable*/ul*cardTableBase;size_tcardTableLength;size_tcardTableMaxLength;size_tcardTableOffset;;这个结构体定义在文件dalvik/vm/alloc/HeapIntemal.h中其中,成员变量cardTableBasecardTableMaxLength描述的是创建的CardTable和起始地址和大小成员变量cardTableLength描述的当前CardTable使用的大小成员变量cardTableMaxLength和cardTableLength的关系就对应于Java堆的最大值MaximumSize和增长上限值GrowthLimit的关系CardTable在真正使用的时候,并不是从成员变量cardTableBase描述的起始地址开始的,而是从一个相对起始地址有一定偏移的位置开始的这个偏移量记录在成员变量cardTableOffset中相应地,Java堆的起始地址和CardTable的偏移地址的差值记录在全局变量gDvm指向的结构体DvmGlobals的成员变量biasedCardTableBase按照函数dvmCardTableStartup前面的注释,之所以要这样做,是为了避免JIT在CardTable伪造假值至于JIT会在CardTable伪造假值的原因,就不得而知,因为还没有研究JIT在此也希望了解的同学可以告诉一下老罗前面我们提到,在CardTable中,用一个byte来描述GC_CARD_SIZE个对象GC_CARD_SIZE是一个宏,它的定义如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片#defineGC_CARD_SHIFT7#defineGC_CARD_SIZE1«GC_CARD_SHIFT这两个宏定义在文件dalvik/vm/alloc/CardTable.h中也就是说,在CardTabic中,用一个byte来描述128个对象每当一个对象在ConcurrentGC的过程中被修改时典型的情景就是我们通过函数dvmSetFieldObje修改了该对象的引用类型的成员变量在这种情况下,该对象在CardTable中对应的字节会被设置为GC_CARD_DIRTYo相反,如果一个对象在ConcurrentGC的过程中没有被修改,那么它在CardTable中对应的字节会保持为GC_CARD_CLEANoGC_CARD_DIRTY和GC_CARD_CLEAN是两个宏,它们的定义在如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片#defineGC_CARD_CLEAN0#defineGC_CARD_DIRTY0x70这两个宏定义在文件dalvik/vm/alloc/CardTable.h中接下来我们再通过四个函数dvmIsValidCarddvmCardFromAddrdvmAddrFromCard和dvmMarkCard来进一步理解CardTable和对象地址的关系函数dvmlsValidCard的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片/*Returnstrueifftheaddressiswithintheboundsofthecardtable.*/booldvmIsValidCardconstul*cardAddrGcHeap*h=gDvm.gcHeap;ul*begin=h-cardTableBase+h-cardTableOffset;ul*end=begin[h-cardTableLength];returncardAddr=begincardAddrend;这个函数定义在文件dalvik/vm/alloc/CardTable.cpp中参数cardAddr描述的是一个CardTable内部的地址由于上述的偏移地址的存在,并不是所有的CardTable内部地址都是正确的CardTable地址只有大于等于偏移地址并且小于当前使用的地址的地址才是正确的地址CardTable的起始地址记录在GcHeap结构体的成员变量cardTableBase中,而偏移量记录在另外一个成员变量cardTableOffset中,因此将这两个值相加即可得到CardTable的偏移地址另外,当前CardTable使用的大小记录在GcHeap结构体的成员变量cardTableLength中,因此,通过这些信息我们就可以判断参数cardAddr描述的是否是一个正确的CardTable地址函数dvmCardFromAddr的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片/*Returnstheaddressoftherelevantbyteinthecardtablegivenanaddressontheheap.*/ul*dvmCardFromAddrconstvoid*addrul^biasedBase=gDvm.biasedCardTableBase;ul*cardAddr=biasedBase+uintptr_taddr»GC_CARD_SHIFT;assertdvmIsValidCardcardAddr;returncardAddr;这个函数定义在文件dalvik/vm/alloc/CardTable.cpp中参数addr描述的是一个对象地址,函数dvmCardFromAddr返回它在CardTable中对应的字节的地址全局变量gDvm指向的结构体DvmGlobals的成员变量biasedCardTableBase记录的是Java堆的起始地址与CardTable的偏移地址的差值将参数addr的值左移GC_CARD_SHIFT位,相当于是得到对象addr在CardTable的字节索引值将这个索引值加上Java堆的起始地址与CardTable的偏移地址的差值,即可得到对象addr在CardTable中对应的字节的地址函数dvmAddrFromCard的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片/*Returnsthefirstaddressintheheapwhichmapstothiscard.*/void*dvmAddrFromCardconstul^cardAddrassertdvm!sValidCardcardAddr;uintptr_toffset=cardAddr-gDvm.biasedCardTableBase;returnvoidoffset«GC_CARD_SHIFT;}这个函数定义在文件dalv汰/vm/alloc/CardTable.cpp中参数cardAddr描述的是一个CardTable地址,函数dvmAddrFromCard返回它对应的对象的地址,它所执行的操作刚刚是和上面分析的函数dvmCardFromAddr相反在此这里不再多述,同学会自己体会一下函数dvmMarkCard的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片/*Dirtiesthecardforthegivenaddress.*/voiddvmMarkCardconstvoid*addrul.cardAddr=dvmCardFromAddraddr;^cardAddr=GC_CARD_DIRTY;这个函数定义在文件dalvik/vm/alloc/CardTable.cpp中在ConcurrentGC执行的过程中,如果修改了一个对象的类型为引用的成员变量,那么就需要调用函数dvmMarkCard来将该对象在CardTable中对应的字节设置为GC_CARD_DIRTY以便后面可以对这个对象进行特殊的处理这个特殊的处理我们后面分析Dalvik虚拟机的垃圾收集过程时再分析函数dvmMarkCard的实现很简单,它首先是通过函数dvmCardFromAddr找至IJ对象在CardTable中对应的字节的地址,然后再将访字节的值设置为GC_CARD_DIRTYO有了这些基础知识之后,回至U函数dvmCardTableStartup中,我们需要知道要创建的CardTable的大小以及该CardTable使用的偏移量正常来说,我们需要的CardTable的大小为heapMaximumSize/GC_CARD_SIZE其中,heapMaximumSize为Java堆的大小但是前面分析的偏移量的存在,我们需要额外的一些内存额外的内存大小为0x100即256个字节因此,我们最终需要的CardTable的大小length就为heapMaximumSize/GC_CARD_SIZE+0x100我们通过调用函数dvmAllocRegion来创建CardTable得到其起始地址为allocBaseo接下来就可以计算CardTable使用的偏移地址首先是计算一个偏移地址biasedBase ul*uintptr_tallocBase-uintptr_theapBase»GC_CARD_SHIFT其中,heapBase是Java堆的起始地址用GC_CARD_DIRTY的值减去biasedBase地址的低8位,就可以得到一个初始偏移量offset GC_CARD_DIRTY-uintptr_tbiasedBaseOxffGC_CARD_DIRTY的值定义为0x70biasedBase地址的低8位描述的值界于0和Oxff之间,因此,上面计算得到的offset可能为负数在这种情况下,需要将它的值加上256这是因为我们需要保证CardTable使用的偏移量是正数最终得到的偏移量如下所示offset+offset00x100:0这里之所以是加上256是因为我们在创建CardTable的时候,额外地增加了256个字节,因此这里不仅可以保证偏移量是正数,还可以保证最终使用的CardTable不会超出前面通过调用函数dvmAllocRegion创建的内存块范围上述计算得到的偏移量保存在gcHeap・cardTableOffset中相应地,Java堆的起始地址和CardTable使用的偏移地址的差值需要调整为biasedBase+gcHeap-cardTableOffset得到的结果保存在gDvm.biasedCardTableBase中这里之所以要采取这么奇怪的算法来给CardTable设置一个偏移量,就是为了前面说的,避免JIT在CardTable伪造假值至此,我们就分析完成Dalvik虚拟机在启动的过程中创建Java堆及其相关的MarkHeapBitmap、LiveHeapBitmapMarkStack和CardTable数据结构了Java堆的起始大小StartingSize指定了Davlik虚拟机在启动的时候向系统申请的物理内存的大小后面再根据需要逐渐向系统申请更多的物理内存,直到达到最大值MaximumSize为止这是一种按需要分配策略,可以避免内存浪费在默认情况下,Java堆的起始大小StartingSize和最大值MaximumSize等于4M和16M但是厂商会通过dalvik.vm.heapstartsize和dalvik.vm.heapsize这两个属性将它们设置为合适设备的值的注意,虽然Java堆使用的物理内存是按需要分配的,但是它使用的虚拟内存的总大小却是需要在Dalvik启动的时候就确定的这个虚拟内存的大小就等于Java堆的最大值MaximumSizeo想象一下,如果不这样做的话,会出现什么情况假设开始时创建的虚拟内存小于Java堆的最大值MaximumSize由于实际情况是允许虚拟内存的大小是达到Java堆的最大值MaximumSize的,因此,当开始时创建的虚拟内存无法满足需求时,那么就需要重新创建另外一块更大的虚拟内存这样就需要将之前的虚拟内存的内容拷贝到新创建的更大的虚拟内存去,并且还要相应地修改各种辅助数据结构这样太麻烦了,而且效率也太低了因此就在一开始的时候,就创建一块与Java堆的最大值MaximumSize相等的虚拟内存但是,Dalvik虚拟机又希望能够动态地调整Java堆的可用最大值,于是就出现了一个称为增长上限的值GrowthLimito这个增长上限值GrowthLimit我们可以认为它是Java堆大小的软限制,而前面所描述的最大值MaximumSize是Java堆大小的硬限制通过动态地调整增长上限值GrowthLimit就可以实现动态调整Java堆的可用最大值,但是这个增长上限值必须要小于等于最大值MaximumSizeo从函数dvmHe叩Startup的实现可以知道,如果没有指定Java堆的增长上限的值GrowthLimit那么它的值就等于Java堆的最大值MaximumSize事实上,在全局变量gDvm中,除了上面提到的三个信息之外,还有三种信息是与Java堆相关的,它们分别是堆最小空闲值MinFree堆最大空闲值MaxFree和堆目标利用率TargetUtilizationo这三个值可以分别通过Dalvik虚拟机的启动选项-XX:HeapMinFree-XX:HeapMaxFree-XX:HeapTargetUtilization来指定它们用来确保每次GC之后,Java堆已经使用和空闲的内存有一个合适的比例,这样可以尽量地减少GC的次数举个例子说,堆的利用率为U最小空闲值为MinFree字节,最大空闲值为MaxFree字节假设在某一次GC之后,存活对象占用内存的大小为LiveSize那么这时候堆的理想大小应该为LiveSize/U但是LiveSize/U必须大于等于LiveSize+MinFree并且小于等于LiveSize+MaxFreeo了解了这些与Java堆大小相关的信息之后,我们回至U函数dvmGcStartup中,可以清楚看到,它先是调用函数dvmHeapSourceStartup来创建一个Java堆,接着再调用函数dvmCardTableStartup来为该Java堆创建一个CardTable接下来我们先分析函数dvmHeapSourceStartup的实现,接着再分析函数dvmCardTableStartup的实现函数dvmHeapSourceStartup的实现如下所示[epp]viewplaincopy在CODE上查看代码片派生到我的代码片GcHeap*dvmHeapSourceStartupsize_tstartSizesize_tmaximumSizesize_tgrowthLimitGcHeap*gcHeap;HeapSource*hs;mspacemsp;sizetlength;Dvoid*base;Allocateacontiguousregionofvirtualmemorytosubdividedamongtheheapsmanagedbythegarbagecollector./length=ALIGN_UP_TO_PAGE_SIZEmaximumSize;base=dvmAllocRegionlengthPROT_NONEgDvm.zygotedalvik-zygotendalvik-heapH;/*Createanunlockeddlmallocmspacetouseasaheapsource.刃msp=createMspacebaseklnitialMorecoreStartstartSize;gcHeap=GcHeap*calloclsizeof*gcHeap;hs=HeapSource*calloclsizeof*hs;hs-targetUtilization=gDvm.heapTargetUtilization*HEAP_UTILIZATTON_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!addInitialHeaphsmspgrowthLimit{if!dvmHeapBitmapIniths-liveBitsbaselengthndalvik-bitmap-r{if!dvmHeapBitmapIniths-markBitsbaselengthndalvik-bitmap-2n{if!allocMarkStackgcHeap-markContext.stackhs-maximumSize{gcHeap-markContext.bitmap=hs-markBits;gcHeap-heapSource=hs;gHs=hs;returngcHeap;}这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中函数dvmHe叩SourceStartup的执行过程如下所示.将参数maximum指定的最大堆大小对齐到内存页边界,得到结果为length并且调用函数dvmAllocRegion分配一块大小等于length的匿名共享内存块,起始地址为base这块匿名共享内存即作为Dalvik虚拟机的Java堆.调用函数createMspace将前面得到的匿名共享内存块封装为一个mspace以便后面可以通过C库得供的mspace_malloc和mspace_bulk_free等函数来管理Java堆这个mspace的起始大小为Java堆的起始大小,这意味着一开始在该mspace上能够分配的内存不能超过Java堆的起始大小不过后面我们动态地调整这个mspace的大小,使得它可以使用更多的内存,但是不能超过Java堆的最大值3分配一个GcHeap结构体gcHeap和一个HeapSource结构体hs用来维护Java堆的信息,包括Java堆的目标利用率、最小空闲值、最大空闲值、起始大小、最大值、增长上限值、堆个数、起始地址和大小等信信息.调用函数addlnitialHeap在前面得到的匿名共享内存上创建一个Active堆这个Active堆的最大值被设置为Java堆的起始大小.调用函数dvmHeapBitmapInit创建和初始化一个LiveBitmap和一个MarkBitmap它们在GC时会用得到.调用函数allockMarkStack创建和初始化一个MarkStack它在GC时也会用至上
7.将前面创建和初始化好的MarkBitmap和HeapSource结构体hs保存在前面创建的GcHeap结构体gcHeap中,并且将该GcHeap结构体gcHeap返回给调用者同时,HeapSource结构体hs也会保存在全局变量gHs中为了更好地对照图2来理解函数dvmHeapSourceStartup所做的事情接下来我们详细分析上述提到的关键函数dvmAllocRegion、createMspace、addlnitialHeap、dvmHeapBitmapInit和allockMarkStack的实现函数dvmAllocRegion的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片void^dvmAllocRegionsize_tbyteCountintprotconstchar*name{void*base;intfdret;byteCount=ALIGN_UP_TO_PAGE_SIZEbyteCount;fd=ashmem_create_regionnamebyteCount;iffd==-l{returnNULL;base=mmapNULLbyteCountprotMAP_PRIVATEfd0;ret=closefd;ifbase==MAP_FAILED{returnNULL;ifret==-1{munmapbasebyteCount;returnNULL;returnbase;这个函数定义在文件dalvik/vm/Misc.cpp中从这里就可以清楚地看出,函数dvmAllocRegion所做的事情就是调用函数ashmem_create_region来创建一块匿名共享内存关于Android系统的匿名共享内存,可以参考前面Android系统匿名共享内存AshmemAnonymousSharedMemory简要介绍和学习计划一文函数crcatcMspacc的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片staticmspacecreateMspacevoid*beginsize_tmorecoreStartsize_tstartingSize//Clearerrnotoallowstrerroronerror.errno=0;//Allowaccesstoinitalpagesthatwillholdmspace.mprotectbeginmorecoreStartPROT_READ|PROT_WRITE;//Createmspaceusingourbackingstoragestartingatbeginandwithafootprintof//morecoreStart.Dontuseaninternaldlmalloclock.WhenmorecoreStartbytesofmemoryare//exhaustedmorecorewillbecalled.mspacemsp=create_mspace_with_basebeginmorecoreStartfalse/locked*/;ifmsp!=NULL{//Donotallowmorecorerequeststosucceedbeyondthestartingsizeoftheheap.mspace_set_footprint_limitmspstartingSize;}else{ALOGEncreate_mspace_with_basefailed%s”strerrorerrno;returnmsp;这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中参数begin指向前面创建的一块匿名共享内存的起始地址,也就是Java堆的起始地址,函数createMspace通过C库提供的函数create_mspace_with_base将该块匿名共享内存封装成一个mspace并且通过调用C库提供的函数mspace_set_footprint_limit设置该mspace的大小为Java堆的起始大小函数addlnitialHeap的实现如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片staticbooladdInitialHeapHeapSource*hsmspacemspsize_tmaximumSizeasserths!=NULL;assertmsp!=NULL;ifhs-numHeaps!=0{returnfalse;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+klnitialMorecoreStart;hs-numHcaps=1;returntrue;这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中在分析函数addlnitialHeap的实现之前,我们先解释一下两个数据结构HeapSource和Heap在结构体HeapSource中,有一个类型为Heap的数组heaps如下所示:fcpp]viewplaincopy在CODE上查看代码片派生到我的代码片structHeapSource{/*Theheaps;heaps
[0]isalwaystheactiveheapwhichnewobjectsshouldbeallocatedfrom./Heapheaps[HEAP_SOURCE_MAX_HEAP_COUNT];/*Thecurrentnumberofheaps./size_tnumHeaps;);这个结构体定义在文件dalvik/vm/alloc/HeapSource.cpp中这个Heap数组最多有HEAP_SOURCE_MAX_HEAP_COUNT个Heap并且当前拥有的Heap个数记录在numpHeaps中HEAP_SOURCE_MAX_HEAP_COUNT是一个宏,定义为2如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片/*Thelargestnumberofseparateheapswecanhandle.*/#defineHEAP_SOURCE_MAX_HEAP_COUNT2这个宏定义在文件dalvik/vm/alloc/HeapSource.h中这意味着Dalvik虚拟机的Java堆最多可以划分为两个Heap就是图1所示的Active堆和Zygote堆结构Heap的定义如下所示[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片structHeap{/*Themspacetoallocatefrom./mspaccmsp;/*Thelargestsizethatthisheapisallowedtogrowto./size_tmaximumSize;/*Numberofbytesallocatedfromthismspaceforobjectsincludinganyoverhead.ThisvalueisNOTexactandshouldonlybeusedasaninputforcertainheuristics./size_tbytesAllocated;/*Numberofbytesallocatedfromthismspaceatwhichaconcurrentgarbagecollectionwillbestarted./size_tconcurrentStartBytes;/*Numberofobjectscurrentlyallocatedfromthismspace./size_tobjectsAllocated;/*Thelowestaddressofthisheapinclusive.刃char*base;/*Thehighestaddressofthisheapexclusive./charlimit;/*IftheheaphasanmspacethecurrenthighwatermarkinallocationsrequestedviadvmHeapSourceMorecore.*/char*brk;);这个结构体定义在文件dalvik/vm/alloc/HeapSource.cpp中结构体Heap用来描述一个堆,它的各个成员变量的含义如下所示msp描述堆所使用内存块maximumSize描述堆可以使用的最大内存值bytesAllocated描述堆已经分配的字节数concurrentStartBytes描述堆已经分配的内存达到指定值就要触发并行GCobjectsAllocated描述已经分配的对象数base描述堆所使用的内存块的起始地址。