Dalvik虚拟机Java堆创建过程分析_第1页
Dalvik虚拟机Java堆创建过程分析_第2页
Dalvik虚拟机Java堆创建过程分析_第3页
Dalvik虚拟机Java堆创建过程分析_第4页
Dalvik虚拟机Java堆创建过程分析_第5页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Dalvik虚拟机Java堆创建过程分析

使用C/C++开发应用程序最令头痛的问题就是内存管理,慎不留神,要么内存泄漏,要么内

存破坏。虚拟机要解决的问题之一就是帮助应用程序自动分配和释放内存。为了达到这个目

的,虚拟机在启动的时候向操作系统申请•大块内存当作对象堆。之后当应用程序创建对象

时,虚拟机就会在堆上分配合适的内存块。而当对象不再使用时,虚拟机就会将它占用的内

存块归还给堆。Dalvik虚拟机也不例外,本文就分析它的Java堆创建过程。

从前面一文可以知道,在Dalvik虚拟机中,Java堆实际上是由一个Active堆和一个Zygote

堆组成的,如图1所示:

HeapSource

Actr>«HeapZygoteHeap

UrdTabte

jveHeapBitmap

MarkHeapBftm^p

图1Dalvi嘘拟机的Java堆

其中,Zygote堆用来管理Zygote进程在启动过程中预加我和创建的各种对象,而Active堆

是在Zygote进程fork第一个子进程之前创建的。之后无论是Zygoie进程还是其子进程,都

在Active堆上进行对象分配和释放。这样做的H的是使得Zygote进程和其子进程最大限度

地共享Zygote堆所占用的内存。

为了管理Java堆,Dalvik虚拟机需要一些辅助数据结构,包括一个CardTable、两

个HeapBitm叩和一个MarkSlack。CardTable是为了记录在垃圾收集过程中对象的引用情

况的,以便可以实现ConcurrentGo图1的两个HeapBitmap,一个称为LiveHeapBitmap,

用来记录上次GC之后,还存活的对象,另一个称为MarkHeapBitm叩,用来记录当前GC

中还存活的对象。这样,上次GC后存活的但是当前GC不存活的对象,就是需要释放的对

象。Davlk虚拟机使用标记-清除(Mark-Sweep)算法进行GC。在标记阶段,通过一个Mark

Stack来实现递归检查被引用的对象,即在当前GC中存活的对象。有了这个MarkStack,

就可以通过循环来模拟函数递归调用。

Dalvik虚拟机Java堆的创建过程实际上就是上面分析的各种数据结构的创建过程,

它们是在Dalvik虚拟机启动的过程中创建的。接下来,我们就详细分析这个过程。

从前面一文可以知道,Dalvik虚拟机在启动的过程中,会通过调用函数dvmGcSiarlup

来创建Java堆,它的实现如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

booldvmGcStartupO

(

dvmInitMutex(&gDvni.gcHeapLock);

pthread_cond_init(&gDvm.gcHeapCond,NULL);

returndvmHeapStartupO;

)

这个函数定义在文件dalvik/vm/alloc/Alloc.cpp中。

函数dvmGcStartup首先是分别初始化一个锁和一个条件变量,它们都是用来保护堆

的并行访问的,接着再调用另夕I、一个函数dvmHeapStartup来创建Java堆。

函数dvmHeapStartup的实现如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

booldvmHeapStartupO

(

GcIIeap*gclleap;

if(gDvm.heapGrowthLimil==0){

gDvm.heapGrowthLimit=gDvm.heapMaximumSize;

)

gcHcap=dvniHcapSourccStariup(gDvm.hcapStartingSize,

gDvm.heapMaximumSize,

gDvm.heapGrowthLimit);

gDvm.gcHeap=gcHcap;

if(!dvmCardTableStanup(gDvm.heapMaximumSize,gDvm.heapGrowthLimit)){

LOGE_HEAP("cardtablestartupfailed.");

returnfalse;

}

returntrue;

I

这个函数定义在文件dalvik/vm/alloc/Heap.cpp中。

gDvm是一个类型为DvmGlobals的全局变量,它通过各个成员变量记录了Dalvik

虚拟机的各种信息。这里涉及到三个重要与Java堆相关的信息,分别是Java堆的起始大小

(StartingSize)^最大值(MaximumSize)和增长上限值(GrowthLimit)®在启动Dalvik

虚拟机的时候,我们可以分别通过-Xms、-Xmx和-XX:HeapGrowthLimil三个选项来指定上

述三个值。

Java堆的起始大小(StartingSize)指定了Davlik虚拟机在启动的时候向系统申请的

物理内存的大小。后面再根据需要逐渐向系统申请更多的物理内存,直到达到最大值

(MaximumSize)为止。这是一种按需要分配策略,可以避免内存浪费。在默认情况下,Java

堆的起始大小(StartingSize)和最大值(MaximumSize)等于4M和16M。但是厂商会通

过dalvik.vm.heapstartsize和dalvik.vm.heapsize这两个属性将它们设置为合适设备的值的。

注意,虽然Java堆使用的物理内存是按需要分配的,但是它使用的虚拟内存的总大

小却是需要在Dalvik启动的时候就确定的。这个虚拟内存的大小就等于Java堆的最大值

(MaximumSize)o想象一下,如果不这样做的话,会出现什么情况。假设开始时创建的虚

拟内存小于Java堆的最大值(MaximumSize),由于实际情况是允许虚拟内存的大小是达到

Java堆的最大值(MaximumSize)的,因此,当开始时创建的虚拟内存无法满足需求时,

那么就需要重新创建另外一块更大的虚拟内存。这样就需要将之前的虚拟内存的内容挑贝到

新创建的更大的虚拟内存去,并且还要相应地修改各种辅助数据结构。这样太麻烦了,而且

效率也太低了。因此就在一开始的时候,就创建一块与Java堆的最大值(MaximumSize)

相等的虚拟内存。

但是,Dalvik虚拟机乂希望能够动态地调整Java堆的可用最大值,于是就出现了一

个称为增长上限的值(GrowthLimit)o这个增长上限值(GrowthLimit),我们可以认为它

是Java堆大小的软限制,而前面所描述的最大值(MaximumSize),是Java堆大小的硬限

制。通过动态地调整增长上限值(GrowthLimit),就可以实现动态调整Java堆的可用最大

值,但是这个增长上限值必须要小于等于最大值(MaximumSize)o从函数dvmHeapSiartup

的实现可以知道,如果没有指定Java堆的增长上限的值(GrowthLimit),那么它的值就等

于Java堆的最大值(MaximumSize)。

事实上,在全局变量gDvm中,除了上面提到的三个信息之外,还有三种信息是与

Java堆相关的,它们分别是堆最小空闲值(MinFree)、堆最大空闲值(MaxFree)和堆目

标利用率(TargetUtilization)(>这三个值可以分别通过Dalvik虚拟机的启动选项

-XX:HeapMinFree、-XX:HcapMaxFree和-XX:HeapTargetUtilizalion来指定。它们用来确保每

次GC之后,Java堆已经使用和空闲的内存有一个合适的比例,这样可以尽量地减少GC的

次数。举个例子说,堆的利用率为U,最小空闲值为MinFree字节,最大空闲值为MaxFree

字节。假设在某一次GC之后,存活对象占用内存的大小为LiveSize。那么这时候堆的理想

大小应该为(LivcSize/U)。但是(LivcSize/U)必须大于等于(LivcSize+MinFree)并且小于等

于(LiveSize+MaxFree)«

了解了这些与Java堆大小相关的信息之后,我们回到函数dvmGcStartup中,可以

清楚看到,它先是调用函数dvniHeapSourceStaitup来创建一个Java堆,接着再调用函数

dvmCardTableStartup来为该Java堆创建一个CardTable。接下来我们先分析函数

dvmHeapSourceStarlup的实现,接着再分析函数dvmCardTableStartup的实现。

函数dvmHeapSourceStartup的实现如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

GcHeap*dvmHeapSourceStartup(size_tstartSize,size_tmaximumSize,

size_tgrowthLimit)

GcHcap*gcHcap;

HeapSource*hs;

mspacemsp;

size_llength;

void*base;

*Allocateacontiguousregionofvirtualmemorytosubdivided

*amongtheheapsmanagedbythegarbagecollector.

*/

length=ALIGN_UP_TO_PAGE_SIZE(maximumSize);

base=dvmAllocRcgion(lengih,PROT_NONE,gDvm.zygote?"dalvik-zygote"

"dalvik-heap");

/*Createanunlockeddlmallocmspacctouseas

*aheapsource.

*/

msp=createMspace(base,klnitialMorecoreStart,startSize);

gcHeap=(GcHeap*)calloc(l,sizeof(*gcHeap));

hs=(HeapSource*)calloc(l,sizeof(*hs));

hs->targetUtilization=gDvm.heapTargetUtilization*HEAP_UTILIZATION_MAX;

hs->minFree=gDvm.heapMinFree;

hs->maxFrcc=gDvm.hcapMaxFrec;

hs->startSize=startSize;

hs->maximumSize=niaximuniSize;

hs->growthLimit=growthLimit;

hs->numHeaps=0;

hs->hcapBasc=(char*)basc;

hs->heapLength=length;

if(!addInitialHeap(hs,msp,growthLimit)){

if(!dvmHeapBitmapInit(&hs->liveBits,base,length,"dalvik-bitmap-r')){

if(!dvmHeapBi(mapInit(&hs->markBits,base,length,'dalvik-bitmap-2")){

if(!allocMarkStack(&gcHeap->>markContext.stack,hs-^maximumSize)){

gcHeap->markContext.bitmap=&hs->markBits;

gcHeap->heapSource=hs;

gHs=hs;

returngcHeap;

这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

函数dvmHeapSourceStartup的执行过程如下所示:

1.将参数maximum指定的最大堆大小对齐到内存页边界,得到结果为length,并

且调用函数dvmAllocRegion分配一块大小等于length的匿名共享内存块,起始地址为base。

这块匿名共享内存即作为Dalvik虚拟机的Java堆。

2.调用函数createMspace将前面得到的匿名共享内存块封装为一个mspace,以便

后面可以通过C库得供的mspace_maUoc和rnspace_bulk_free等函数来管理Java堆。这个

mspace的起始大小为Java堆的起始大小,这意味着一开始在该mspace上能够分配的内存不

能超过Java堆的起始大小。不过后面我们动态地调整这个mspace的大小,使得它可以使用

更多的内存,但是不能超过Java堆的最大值。

3,分配一个GcHeap结构体gcHeap和一个HeapSource结构体hs,用来维护Java

堆的信息,包括Java堆的目标利用率、最小空闲值、最大空闲值、起始大小、最大值、增

长上限值、堆个数、起始地址和大小等信信息。

4.调用函数addlnitialHeap在前面得到的匿名共享内存上创建一个Active堆。这个

Active堆的最大值被设置为Java堆的起始大小。

5.调用函数dvmHeapBitmapInit创建和初始化一个LiveBitmap和一个Mark

Bitmap,它们在GC时会用得到。

6.调用函数allockMarkStack创建和初始化一个MarkStack,它在GC时也会用到。

7.将前面创建和初始化好的MarkBitmap和HeapSource结构体hs保存在前面创建

的GcHcap结构体gcHcap中,并且将该GcHcap结构体gcHcap返回给调用者。同时,

HeapSource结构体hs也会保存在全局变量gHs中。

为了更好地对照图2来理解函数dvmHeapSourceSlartup所做的事情,接下来我们详

细分析上述提到的关键函数dvmAIIocRegion、createMspace、addlnitialHeap%

dvmHeapBitmapInit和allcckMarkStack的实现。

函数dvmAIIocRegion的实现如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

void*dvmAllocRegion(size_tbyteCounl,iniprot,constchar*name){

void*base;

intfd,ret;

bytcCount=ALIGN_UP_TO_PAGE_SlZE(bytcCount);

fd=ashmem_crcatc_rcgion(namc,bytcCount);

if(fd==-1){

returnNULL;

}

base=rnniap(NULL,byteCount,prot,MAP_PRIVATE,fd,0);

ret=close(fd);

if(base==MAP_FAILED){

returnNULL;

)

if(ret==-1){

munniapCbase,byteCount);

returnNULL;

)

returnbase;

}

这个函数定义在文件dalvik/vm/Misc.cpp中。

从这里就可以清楚地看出,函数dvmAIIocRegion所做的事情就是调用函数

ashmem_create_region来创建一块匿名共享内存。关于Android系统的匿名共享内存,可以

参考前面Android系统匿名共享内存Ashmem(AnonymousSharedMemory)简要介绍和学

习计划一文。

函数createMspace的实现如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

staticmspacecreateMspace(void*begin,size_tmorecoreStart,size_tstartingSize)

//Clearerrnotoallowstrerroronerror.

errno=0;

//Allowaccesstoinitaipagesthatwillholdmspace.

mprotcct(bcgin,morccorcStart,PROT_READ|PROT_WRITE);

//Createmspaceusingourbackingstoragestartingatbeginandwithafootprintof

//morecorcStart.Don'tuseaninternaldlmalloclock.WhenmorccoreStartbytesofmemory

are

//exhaustedmorecorewillbecalled.

mspacenisp=create_mspace_vvith_base(begin,morecoreStart,false/*Iocked*/);

if(msp!=NULL){

//Donotallowmorccorcrequeststosucceedbeyondthestartingsizeoftheheap.

mspace_sel_fooiprin(_limit(msp,star(ingSize);

}else{

ALOGE("creale_^ispace_with_basefailed%s",slrerror(ermo));

)

returnmsp;

}

这个函数定义在文件dalvik/vm/aHoc/HcapSourcc.cpp中。

参数begin指向前面创建的一块法名共享内存的起始地址,也就是Java堆的起始地

址,函数createMspace通过C库提供的函数crcatc_mspacc_\vith_basc将该块匿名共享内存

封装成一个mspace,并且通过调用C库提供的函数mspace_sel_foolprint_limit设置该mspace

的大小为Java堆的起始大小。

函数addlnitialHeap的实现如下所示:

[epp]viewplaincopy在CODE上查看代码片派生到我的代码片

staticbooladdInitialHcap(HcapSourcc*hs,mspacemsp,sizs_tmaximumsizc)

(

assert(hs!=NULL);

assert(msp!=NULL);

if(hs->numHeaps!=0){

returnfalse;

)

hs->hcaps[0].msp=msp;

hs->heaps[O].maximunSize=maximumSize;

hs->heaps[0].concurrentStartBytes=SIZE_MAX;

hs->heaps[()].base=hs->heapBase;

hs->heaps[0].limit=hs->heapBase+maximumSize;

hs->heaps[()].brk=hs->heapBase+klnitialMorecoreStart;

hs->nuniHeaps=1;

returntrue;

}

这个函数定义在文件daivik/vm/alloc/HeapSource.cpp中。

在分析函数addlnitialHeap的实现之前,我们先解释一下两个数据结构HeapSource

和He叩。

在结构体HeapSource中,有一个类型为Heap的数组he叩s,如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

structHeapSource{

/*Theheaps;heapsl()jisalwaystheactiveheap,

*whichnewobjectsshouldbeallocatedfrom.

*/

Heapheaps[HEAP_SOURCE_MAX_HEAP_COUNT];

/*Thecurrentnumberofheaps.

*/

size_tnuniHcaps;

};

这个结构体定义在文件dalvik/vm/alloc/HcapSourcc.cpp中。

这个Heap数组最多有HEAP_SOURCE_MAX_HEAP_COUNT个Heap,并且当前拥

有的Heap个数记录在nunipHeaps中。

HEAP_SOURCE_MAX_HEAP_COUNT是一个宏,定义为2,如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

/*Thelargestnumberofseparateheapswccanhandle.

#defineHEAP_SOURCE_MAX_HEAP_COUNT2

这个宏定义在文彳匕dalvik/vm/alloc/HeapSource.h中。

这意味着Dalvik虚拟机的Java堆最多可以划分为两个Heap,就是图1所示的Active

堆和Zygote堆。

结构Heap的定义如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

structHeap(

/*Themspacetoallocatefrom.

*/

mspacemsp;

/*Thelargestsizethatthisheapisallowedtogrowto.

*/

size_tmaximumSize;

/*Numberofbytesallocatedfromthismspaceforobjects,

*includinganyoverhead.ThisvalueisNOTexact,and

*shouldonlybeusedasaninputforcertainheuristics.

*/

size_tbytesAllocated;

/*Numberofbytesallocatedfromthismspaceatwhicha

*concurrentgarbagecollectionwillbestarted.

*/

sizc_tconcurrcntStartBytes;

/*Numberofobjectscurrentlyallocatedfromthismspace.

*/

size_tobjectsAllocated;

/*

*Thelowestaddressofthisheap,inclusive.

*/

char*basc;

/*

*Thehighestaddressofthisheap,exclusive.

*/

char

/*

*Iftheheaphasanmspace,thecurrenthighwatermarkin

*allocationsrequestedviadvmHeapSourceMorecore.

*/

char*brk;

);

这个结构体定义在文件dalvik/vm/alloc/HeapSource.cpp中。

结构体Heap用来描述一个堆,它的各个成员变量的含义如下所示:

msp:描述堆所使用内存块。

maximumSize:描述堆可以使用的最大内存值。

bytesAllocated:描述堆已经分配的字节数。

concurrcntStartBytcs:描述堆已经分配的内存ii到指定值就要触发并行GC。

objectsAllocated:描述已经分配的对象数。

base:描述堆所使用的内存块的起始地址。

limit:描述堆所使用的内存块的结束地址。

brk:描述当前堆所分配的最大内存值。

回到函数addlnitialHeap中,参数hs和msp指向的是在函数dvniHeapSourceSlartup

中创建的HeapSource结构体和mspace内存对象,而参数maximumSize描述的Java堆的增

长上限值。

通过函数addlnitialHeap的实现就可以看出,Dalvik虚拟机在启动的时候,实际上

只创建了一个Heap。这个Heap就是我们在图1中所说的Active堆,它开始的时候管理的

是整个Java堆。但是在图1中,我们说Java堆实际上还包含有一个Zygote堆的,那么这个

Zygote堆是怎么来的呢?

从前面一文可以知道,Zygote进程会通过调用函数forkAndSpecializeCommcn来

fork子进程,其中与Dalvik虚拟机Java堆相关的逻辑如下所示:

[cpplviewplaincopy在CODE上查看代码片派生到我的代码片

staticpid_tforkAndSpccial:zcCommon(constu4*args,boolisSystcmScrvcr)

(

pid_tpid;

if(!dvmGcPrcZygotcFork()){

pid=fork();

if(pid==0){

}else{

)

returnpid;

I

这个函数定义在文件dalvik/vm/native/dalvik_system_Zygote.cpp中。

从这里就可以看出,Zygote进程在fork子进程之前,会调用函数

dvmGcPreZygoteFork来处理一下Dalvik虚拟机Java堆。接下来我们就看看函数

dvmGcPreZygoteFork都做了哪些事情。

函数dvmGcPreZygoteFork的实现如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

booldvniGcPrcZygoteFork()

(

returndvmHcapSourceStartupBcforcFork();

I

这个函数定义在文件dalvik/vm/alloc/Alloc.cpp中。

函数dvmGcPreZygoteFork只是简单地封装了对另外一个函数

dvmHeapSourceStartupBeforeFork的调用,后者的实现如下所示:

fcpp]viewplaincopy在CODE上查看代码片派生到我的代码片

booldvmHeapSourceStartupBeforeForkO

(

HeapSource*hs=gHs;//usealocaltoavoidtheimplicit"volatile"

HS_BOILERPLATE();

assert(gDvm.zygote);

if(JgDvm.newZygoteHeapAllocated){

/*Ensureheapsaretrimmedtominimizefootprintpre-fork.

♦/

trimHeapsO;

/*Createanewheapforpost-forkzygoteallocations.Wconly

*tryonce,evenifitfails.

*/

ALOGV("Splitlingoulnewzygoteheap");

gDvm.newZygoteHeapAllocated=true;

returnaddNewHeap(hs);

)

returntrue;

}

这个函数定义在文件daivik/vm/alloc/HcapSource.cpp4'o

前面我们在分析函数dvmHeapSourceStartup的实现时提到,全局变量gHs指向的

是一个HeapSource结构体,它描述\Dalvik虚拟机Java堆的信息。同时,gDvm也是一个

全局变吊:,它的类型为DvmGlobals。gDvm指向的DvmGlobals结构体的成员变吊:

newZygoteHeapAllocated的值被初始化为false。因此,当函数

dvmHeapSourceStartupBeforeFork第一次被调用时,它会先调用函数trimHeaps来将Java堆

中没有使用到的内存归还给系统,接着再调用函数addNewHeap来创建一个新的Heap。这

个新的Heap就是图1所说的Zygote堆了。

由于函数dvmHeapSourceStartupBeforeFork第一次被调用之后,gDvm指向的

DvmGlobals结构体的成员变量newZygoteHeapAllocated的值就会被修改为(rue,因此起到

的效果就是以后Zygote进程对函数dvmHeapSourceStartupBeforeFork的调用都是无用功。这

也意味着Zygote进程只会在fork第一个子进程的时候,才会将Java堆划一分为二来管理。

接下来我们就继续分析函数trimHcaps和addNcwHcap的实现,以便更好地理解

Dalvik虚拟机是如何管理Java堆的。

函数trimHeaps的实现如下所示:

[cppjviewplaincopy在CODE上查看代码片派生到我的代码片

/*

*Returnunusedmemorytothesystemifpossible.

*/

staticvoidtrimHeaps()

(

HS_BOILERPLATE();

HeapSource*hs=gHs;

sizc_theapBytes=0;

for(sizc_ti=0;i<hs->numlleaps;i++){

Heap*heap=&hs->heaps[i];

/*Returnthewildernesschunktothesystem.*/

inspace_trirn(heap->msp,0);

/*Returnanywholefreepagestothesystem.*/

mspace_inspect_all(heap->msp,releasePagesInRange,&heapBytes);

)

/*Sameforthenativeheap.*/

dlmalloc_trim(0);

size_tnativeBytes=0;

dimalloc_inspect_all(releascPagesInRange,&nativeBytes);

LOGD_HEAP("madviscd%zd(GC)+%zd(native)=%zdtotalbytes",

heapBytes,nativeBytes,heapBytes+nativeBytes);

)

这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

函数trimHeaps对Dalvik虚拟机使用的Java堆和默认Native堆做了同样的两件事

情。

第一件事情是调用C库提供的函数mspacc_trim/d1malloc_trim来将没有使用到的虚

拟内存和物理内存归还给系统,这是通过系统调用mremap来实现的。

第二件事情是调用C库提供的函数mspace_inspect_all/dlmalloc_inspect_all将不能

使用的内存碎片对应的物理内存归还给系统,这是通过系统调用madvise来实现的。注意,

在此种情况下,只能归还无用的物理内存,而不能归还无用的虚拟内存。因为归还内存碎片

对应的虚拟内存会使得堆为整体虚拟地址不连续。

函数addNewHeap的实现如下所示:

[cpp]viewplaincopy在CODE上杳看代码片派生到我的代码片

staticbooladdNewHeap(HeapSource*hs)

(

Heapheap;

asserl(hs!=NULL);

if(hs->numHcaps>=HEAP_SOURCE_MAX_HEAP_COUNT){

returnfalse;

)

mcmsct(&hcap,0,sizcof(hcap));

/*

*Heapstoragecomesfromacommonvirtualmemoryreservation.

*Thenewheapwillstartonthepageaftertheoldheap.

*/

char*base=hs->he叩s[O].brk;

sizc_toverhead=base-hs->hcaps[O].base;

assert(((size_t)hs->heaps[O].base&(SYSTEM_PAGE_SIZE-1))==0);

if(overhead+hs->mirFree>=hs->maximumSize){

returnfalse;

}

size_tmorecoreStart=SYSTEM_PAGE_SIZE;

heap.maximumSize=hs->growthLimit-overhead;

hcap.concurrcntStartBytes=hs->minFrec-CONCURRENT_START;

heap.base=base;

heap.limit=heap,base+heap.maximumSize;

heap.brk=heap.base+morecoreStart;

if(!「emapNewHe叩(hs.&heap)){

returnfalse;

}

hcap.msp=crcatcMspacc(basc,morecoreStart.hs->ininFrcc);

if(heap.msp==NULL){

returnfalse;

/*Don'tletthesoon-tc-be-oldheapgrowanyfurther.

*/

hs->hcaps[O].inaximumSizc=overhead;

hs->heaps[OJ.limit=base;

mspace_set_footprint_liniit(hs->heaps[0].msp,overhead);

/*Putthenewhe叩inthelist,atheaps[0].

*Shiftexistingheapsdown.

*/

memmove(&hs->heaps[1],&hs->heaps[0],hs->numHeaps*sizcof(hs->hcaps[OJ));

hs->heaps[0]=heap;

hs->numHeaps++;

returntrue;

这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

函数addNcwHcap所做的事情实际上就是将前面创建的Dalvik虚拟机Java堆一分

为二,得到两个Heap。

在划分之前,HeadSource结构体hs只有一个Heap,如图2所示:

overhwd

ta->heap$lo).basehs->heaps(0]brkhs->heap$[0].Wnrt

图2Dalvi嘘拟机Java堆一分为二之前

接下来在未使用的Dalvi嘘拟机Java堆中创建另外一个Heap,如图3所示:

h$->hejps[O].t>asehs->hups[0].trkhwnMheap.bm»t

图3在未使用的Dalvi嘘拟机Java地中创建一个新的Heap

最后调整HeadSource结构体hs的heaps数组,即交heaps网和heaps⑴的值,结果

如图4所示:

hs->hea3s[l].tanrths->heap5(O].ba$e

overhead

h5->heaps⑴.basehs->he«pS[l].br1chS->heap$[O].brkhs->he«p5|0pimrt

图4DaM嘘拟机Java堆一分为二之后

其中,heaps[l]就是我们在图।中所说的Zygote堆,而heaps⑼就是我们在图1中所说的Active

堆。以后无论是Zygote进程,还是Zygote子进程,需要分配对象时,都在Active堆上进行。

这样就可以使得Zygote城最大限度地在Zygote进程及其子进程中共享。

这样我们就分析完了函数addlnitialHcap及其相关函数的实现,接下来我们继续分析

函数dvmHeapBitmapInil和allocMarkSlack的实现。

函数dvniHeapBicmapInit的实现如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

booldvmHeapBitmapInil(HeapBilmap*hb,constvoid*base,size」maxSize,

constchar*name)

(

void*bits;

size_tbitsLen;

asscrt(hb!=NULL);

asscrt(name!=NULL);

bitsLcn=HB_OFFSET_TO」NDEX(maxSize)*sizcof(*hb->bits);

bits=dvmAllocRegion(bilsLen,PROT_READ|PROT_WRITE,name);

if(bits==NULL){

ALOGE("Couldnotnunap%zd-byteashmemregion'%s"\bitsLen,name);

returnfalse;

)

hb->bits=(unsignedlong*)bits;

hb->bitsLcn=hb->allocLcn=bitsLcn;

hb->base=(uintptr_t)base;

hb->max=hb->base-1;

returntrue;

)

这个函数定义在文件dalvik/vm/alloc/HeapBitmap.cpp中。

参数hb指向一个HcapBitmap结构体,这个结构体正是函数dvmHcapBitmapInit要

进行初始化的。参数base和maxSize描述的是Java堆的起始地址和大小。另外一个参数name

描述的是参数hb指向的HeapBitm叩结构体的名称。

在分析函数dvmHeapBitmapInit的实现之前,我们先来了解一下结构体HeapBitmap

的定义,如下所示:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

structHeapBmap{

/*Thebitmapdata,whichpointstoanmniap()edareacfzeroed

*anonymousmemory.

*/

unsignedlong*bits;

/*Thesizeoftheusedmemorypointedtobybits,inbytes.This

*valuechangeswhenthebitmapisshrunk.

*/

size_tbitsLen;

/*Therealsizeofthememorypointedtobybits.Thisisthe

*numberofbyteswerequestedfromtheallocatoranddoesnot

*change.

*/

size_tallocLcn;

/*Thebaseaddress,whichcorrespondstothefirstbitin

*thebitmap.

*/

uintptr_tbase;

/*Thehighestpointervalueeverreturnedbyanallocation

*from(hisheap.I.e.,(hehighestaddressthatmaycorrespond

*toasetbit.Iftherearenobitsset,(max<base).

*/

uintptr_tmax;

);

这个结构体定义在文件dalvik/vm/alloc/HeapBilir.ap.h。

代码对HcapBitm叩结构体的各个成员变量的含义已经有很详细的注释,其中最重

要的就是成员变量bits指向的一个类型为unsignedlong的数组,这个数组的每一个bit都用

来标记一个对象是否存活,

回到函数dvmHeapBitmapInit中,Java堆的起始地址为base,大小为maxSize,由此

我们就知道,在Java堆上创建的对象的地址范围为[base,maxSize)。但是通过C库提供的

mspace_malloc来在Java堆分配内存时,得到的内存地虻是以8字节对齐的。这意味着我们

只需要(maxSize/8)个bit来描述Java堆的对象。结构体HcapBitmap的成员变量bits是一个

类型为unsignedlong的数组,也就是说,数组中的每一个元素都可以描述sizeof(unsignedlong)

个对象的存活。在32位设备上,一个unsignedlong占用32个bit,这意味着需要一个大小

为(maxSize/8/32)的unsignedlong数组来描述Java堆对象的存活。如果换成字节数来描述

的话,就是说我们需要一块大小为(maxSize/8/32)X4的内存块来描述一个大小为

maxSize的Java堆对象。

Dalvik虚拟机提供了一些宏来描述对象地址与HcapBitmap结构体的成员变量bits所

描述的unsignedlong数组的关系,如下所示:

[epp]viewplaincopy在CODE上查看代码片派生到我的代码片

#defineHB_OBJECT_ALIGNMENT8

#defineHB_BITS_PER_WORD(sizeof(unsignedlong)*CHAR_BIT)

/*<offsct>isthedifferencefrom.basetoapointeraddress.

*<index>istheindexof.bitsthatcontainsthebitrepresenting

*<offset>.

*/

#defineHB_OFFSET_TOJNDEX(offset_)\

((uintptr_t)(offset_)/HB_OBJECT_AL1GNMENT/HB_BITS_PER_WORD)

#defineHB_INDEX_TO_OFFSET(index.)\

((uintptr_t)(index_)*HB_OBJECT_ALIGNMENT*HB_BITS_PER_WORD)

#defineHB_OFFSET_TO_BYTE」NDEX(offset_)\

(HB_OFFSET_TO_INDEX(offset_)*sizeof(*((HeapBitmap*)0)->bils))

这些宏定义在文巳dalvik/vm/alloc/HeapBitmap.h中。

假设我们知道了一个对象的地址为ptr,Java堆的起始地址为base,那么就可以计算

得到一个偏移值offset。有了这个偏移值之后,就可以通过宏HB_OFFSET_TO」NDEX计算

得到用来描述该对象存活的bit位于HcapBitmap结构体的成员变量bits所描述的unsigned

long数组的索引index。有了这个index之后,我们就可以得到一个unsignedlong值。接着

再通过对象地址ptr的第4到第8位表示的数值为索引,在前面找到的unsignedlong值取出

相应的位,就可以得到该对象是否存活了。

相反,给出一个HeapBitmap结构体的成员变显bits所描述的unsignedlong数组的

索引index,我们可以通过宏HB」NDEX_TO_OFFSET找到一个偏移值offset,将这个偏移

值加上Java堆的起始地址base,就可以得到一个Java对象的地址ptr。

第三个宏HB_OFFSET_TO_BYTEJNDEX借助宏HB_OFFSET_TO_INDEX来找出

用来描述对象存活的bitHeapBitmap结构体的成员变量bits所描述的内存块的字节索弓I。

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

接下来我们再来看函数allocMarkStack的实现,如下所示:

[epp]viewplaincopy在CODE上查看代码片派生到我的代码片

staticboolallocMarkStack(GcMarkS(ack*s(ack,size_tmaximumSize)

(

constchar*name="dalvik-mark-stack";

void*addr;

assert(stack!=NULL);

stack->lcngth=maximumSize*sizcof(Objcct*)/

(sizeof(Object)+HEAP_SOURCE_CHUNK_OVERHEAD);

addr=dvmAllocRegicn(stack->length,PROT_READ|PROT_WRITE,name);

if(addr==NULL){

returnfalse;

stack->base=(constObject**)addr;

stack->limit=(constObject**)((char*)addr+stack->length);

stack->top=NULL;

niadvise(stack->base,stack->length,MADV_DONTNEED);

returntrue;

)

这个函数定义在文件vm/alloc/HeapSource.cpp中。

参数stack指向的是一个GcMarkStack结构体,这个结构体正是函数allocMarkStack

要进行初始化的。参数maximumSize描述的是Java堆的大小。

同样是在分析函数allocMarkStack的实现之前,我们先来了解一下结构体

GcMarkStack的定义,如下所示:

[epp]viewplaincopy在CODE上查看代码片派生到我的代码片

structGcMarkStack{

/*Highestaddress(exclusive)

*/

constObject

/*Currenttopofthestack(exclusive)

*/

constObject**top;

/*Lowestaddress(inclusive)

*/

constObject**base;

/*Maximumstacksize,inbytes.

*/

size_tlength;

};

这个结构体定义在文件dalvik/vm/alloc/MarkSwecp.h<>

代码对HeapBitmap结构体的各个成员变量的含义己经有很详细的注释。总结来说,

GcMarkStack通过一个Object*数组来描述一个栈。这人Object*数组的大小通过成员变量

length来描述。成员变量base和limit分别描述栈的最低地址和最高地址,另外一个成员变

量top指向栈顶。

回到函数allocMarkStack中,我们分析一下需要一个多大的栈来描述Java堆的所有

对象。首先,每一个Java对象都是必须要从Object结构体继承卜来的,这意味着每一个Java

对象占用的内存都至少为sizeof(Object)。其次,通过C库提供的接口mspace_mallocJava

堆上为对象分配内存时,C库自己需要•些额外的内存来管理该块内存,例如用额外的4个

字节来记录分配出去的内存块的大小。额外需要的内存大小通过宏

HEAP_SOURCE_CHUNK_OVERHEAD来描述。最后,我们就可以知道,一个大小为

maximumSize的Java堆,在最坏情况下,存在(maximumSize/(sizeof(Object)+

HEAP_SOURCE_CHUNK_OVERHEAD))个对象。也就是说,GcMarkStack通过一个大小为

(maximumSize/(sizcof(Objcct)+HEAP_SOURCE_CHUNK_OVERHEAD))WObject*数组来

描述一个栈。如果换成字节数来描述的话,就是说我们需要一块大小为(maximumSize*

sizeof(Object*)/(sizeof(Object)+HEAP_SOURCE_CHLNK_OVERHEAD))的内存块来描述

一个GcMarkStack栈。

有了上述的基础知识之后,函数allocMarkStack的实现同样也一目了然了。

这样,函数dvmHcapSourccStartup及其相关的函数dvmAilocRcgion>crcatcMspace>

addInitialHeap>dvmHeapBiimapInil和allockMarkSlack的实现我们就分析完了,回到前面的

函数dvmHeapStailup中,它调用函数dvmHeapSourceStartup创建完成Java堆及其相关的

HeapBitmap和MarkSlack之后,还需要继续调用函数dvmCardTableStartup来创建

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论