ART运行时为新创建对象分配内存的过程分析.docx

上传人:b****6 文档编号:7957632 上传时间:2023-01-27 格式:DOCX 页数:18 大小:87.58KB
下载 相关 举报
ART运行时为新创建对象分配内存的过程分析.docx_第1页
第1页 / 共18页
ART运行时为新创建对象分配内存的过程分析.docx_第2页
第2页 / 共18页
ART运行时为新创建对象分配内存的过程分析.docx_第3页
第3页 / 共18页
ART运行时为新创建对象分配内存的过程分析.docx_第4页
第4页 / 共18页
ART运行时为新创建对象分配内存的过程分析.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

ART运行时为新创建对象分配内存的过程分析.docx

《ART运行时为新创建对象分配内存的过程分析.docx》由会员分享,可在线阅读,更多相关《ART运行时为新创建对象分配内存的过程分析.docx(18页珍藏版)》请在冰豆网上搜索。

ART运行时为新创建对象分配内存的过程分析.docx

ART运行时为新创建对象分配内存的过程分析

ART运行时为新创建对象分配内存的过程分析

ART运行时和Dalvik虚拟机一样,在堆上为对象分配内存时都要解决内存碎片和内存不足问题。

内存碎片问题可以使用dlmalloc技术解决。

内存不足问题则通过垃圾回收和在允许范围内增长堆大小解决。

由于垃圾回收会影响程序,因此ART运行时采用力度从小到大的进垃圾回收策略。

一旦力度小的垃圾回收执行过后能满足分配要求,那就不需要进行力度大的垃圾回收了。

本文就详细分析ART运行时在堆上为对象分配内存的过程。

从前面一文可以知道,在ART运行时中,主要用来分配对象的堆空间ZygoteSpace和AllocationSpace的底层使用的都是匿名共享内存,并且通过C库提供的malloc和free接口来分进行管理。

这样就可以通过dlmalloc技术来尽量解决碎片问题。

这一点与我们在前面一文提到的Dalvik虚拟机解决堆内存碎片问题的方法是一样的。

因此,接下来在分析ART运行时为新创建对象分配的过程中,主要会分析它是如何解决内存不足的问题的。

ART运行时为新创建对象分配的过程如图1所示:

对比一文的图2,可以发现,ART运行时和Dalvik虚拟机为新创建对象分配内存的过程几乎是一模一样的,它们的区别仅仅是在于垃圾收集的方式和策略不同。

从前面一文可以知道,ART运行时为从DEX字节码翻译得到的Native代码提供的一个函数调用表中,有一个pAllocObject接口,是用来分配对象的。

当ART运行时以Quick模式运行在ARM体系结构时,上述提到的pAllocObject接口由函数art_quick_alloc_object来实现。

因此,接下来我们就从函数art_quick_alloc_object的实现开始分析ART运行时为新创建对象分配内存的过程。

函数art_quick_alloc_object的实现如下所示:

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

/*

*Calledbymanagedcodetoallocateanobject

*/

.externartAllocObjectFromCode

ENTRYart_quick_alloc_object

SETUP_REF_ONLY_CALLEE_SAVE_FRAME@savecalleesavesincaseofGC

movr2,r9@passThread:

:

Current

movr3,sp@passSP

blartAllocObjectFromCode@(uint32_ttype_idx,Method*method,Thread*,SP)

RESTORE_REF_ONLY_CALLEE_SAVE_FRAME

RETURN_IF_RESULT_IS_NON_ZERO

DELIVER_PENDING_EXCEPTION

ENDart_quick_alloc_object

这个函数定义在文件art/runtime/arch/arm/quick_entrypoints_arm.S中。

这是一段ARM汇编,我们需要注意的一点是Native代码调用ART运行时提供的对象分配接口的参数传递方式。

其中,参数type_idx描述的是要分配的对象的类型,通过寄存器r0传递,参数method描述的是当前调用的类方法,通过寄存器r1传递。

函数art_quick_alloc_object是通过调用另外一个函数artAllocObjectFromCode来分配对象的。

函数art_quick_alloc_object除了传递前面描述的参数type_idx和method给函数artAllocObjectFromCode之外,还会传递另外的两个参数。

其中一个是描述当前线程的一个Thread对象,该对象总是保存在寄存器r9中,现在由于要通过参数的形式传递给另外一个函数,因此就将它放在寄存器r2。

另外一个是栈指针sp,也是由于要通过参数的形式的传递另外一个函数,这里也会将它放在寄存器r3中。

函数artAllocObjectFromCode的实现如下所示:

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

extern"C"mirror:

:

Object*artAllocObjectFromCode(uint32_ttype_idx,mirror:

:

ArtMethod*method,

Thread*self,mirror:

:

ArtMethod**sp)

SHARED_LOCKS_REQUIRED(Locks:

:

mutator_lock_){

FinishCalleeSaveFrameSetup(self,sp,Runtime:

:

kRefsOnly);

returnAllocObjectFromCode(type_idx,method,self,false);

}

这个函数定义在文件art/runtime/entrypoints/quick/quick_alloc_entrypoints.cc中。

函数artAllocObjectFromCode又是通过调用另外一个函数AllocObjectFromCode来分配对象的。

不过,在调用函数AllocObjectFromCode之前,函数artAllocObjectFromCode会先调用另外一个函数FinishCalleeSaveFrameSetup在当前调用栈帧中保存一个运行时信息。

这个运行时信息描述的是接下来要调用的方法的类型为Runtime:

:

kRefsOnly,也就是由被调用者保存那些不是用来传递参数的通用寄存器,即除了r0-r3的其它通用寄存器。

函数AllocObjectFromCode的实现如下所示:

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

//GiventhecontextofacallingMethod,useitsDexCachetoresolveatypetoaClass.Ifit

//cannotberesolved,throwanerror.Ifitcan,useittocreateaninstance.

//Whenverification/compilerhasn'tbeenabletoverifyaccess,optionallyperformanaccess

//check.

staticinlinemirror:

:

Object*AllocObjectFromCode(uint32_ttype_idx,mirror:

:

ArtMethod*method,

Thread*self,

boolaccess_check)

SHARED_LOCKS_REQUIRED(Locks:

:

mutator_lock_){

mirror:

:

Class*klass=method->GetDexCacheResolvedTypes()->Get(type_idx);

Runtime*runtime=Runtime:

:

Current();

if(UNLIKELY(klass==NULL)){

klass=runtime->GetClassLinker()->ResolveType(type_idx,method);

if(klass==NULL){

DCHECK(self->IsExceptionPending());

returnNULL;//Failure

}

}

if(access_check){

if(UNLIKELY(!

klass->IsInstantiable())){

ThrowLocationthrow_location=self->GetCurrentLocationForThrow();

self->ThrowNewException(throw_location,"Ljava/lang/InstantiationError;",

PrettyDescriptor(klass).c_str());

returnNULL;//Failure

}

mirror:

:

Class*referrer=method->GetDeclaringClass();

if(UNLIKELY(!

referrer->CanAccess(klass))){

ThrowIllegalAccessErrorClass(referrer,klass);

returnNULL;//Failure

}

}

if(!

klass->IsInitialized()&&

!

runtime->GetClassLinker()->EnsureInitialized(klass,true,true)){

DCHECK(self->IsExceptionPending());

returnNULL;//Failure

}

returnklass->AllocObject(self);

}

这个函数定义在文件art/runtime/entrypoints/entrypoint_utils.h中。

参数type_idx描述的是要分配的对象的类型,函数AllocObjectFromCode需要将它解析为一个Class对象,以便可以获得更多的信息进行内存分配。

函数AllocObjectFromCode首先是在当前调用类方法method的DexCache中检查是否已经存在一个与参数type_idx对应的Class对象。

如果已经存在,那么就说明参数type_idx描述的对象类型已经被加载和解析过了,因此这时候就可以直接拿来使用。

否则的话,就通过调用保存在当前运行时对象内部的一个ClassLinker对象的成员函数ResolveType来对参数type_idx描述的对象类型进行加载和解析。

关于DexCache的知识,可以参数前面一文,而对象类型(即类)的加载和解析过程可以参考前面一文。

得到了要分配的对象的类型klass之后,如果参数access_check的值等于true,那么就对该类型进行检查,即检查它是否可以实例化以及是否可以访问。

如果检查通过,或者不需要检查,那么接下来还要确保类型klass是已经初始化过了的。

前面的检查都没有问题之后,最后函数AllocObjectFromCode就调用Class类的成员函数AllocObject来分配一个类型为klass的对象。

Class类的成员函数AllocObject的实现如下所示:

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

Object*Class:

:

AllocObject(Thread*self){

......

returnRuntime:

:

Current()->GetHeap()->AllocObject(self,this,this->object_size_);

}

这个函数定义在文件art/runtime/mirror/class.cc中。

这里我们就终于看到调用ART运行时内部的Heap对象的成员函数AllocObject在堆上分配对象了,其中,要分配的大小保存在当前Class对象的成员变量object_size_中。

Heap类的成员函数AllocObject的实现如下所示:

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

mirror:

:

Object*Heap:

:

AllocObject(Thread*self,mirror:

:

Class*c,size_tbyte_count){

......

mirror:

:

Object*obj=NULL;

size_tbytes_allocated=0;

......

boollarge_object_allocation=

byte_count>=large_object_threshold_&&have_zygote_space_&&c->IsPrimitiveArray();

if(UNLIKELY(large_object_allocation)){

obj=Allocate(self,large_object_space_,byte_count,&bytes_allocated);

......

}else{

obj=Allocate(self,alloc_space_,byte_count,&bytes_allocated);

......

}

if(LIKELY(obj!

=NULL)){

obj->SetClass(c);

......

RecordAllocation(bytes_allocated,obj);

......

if(UNLIKELY(static_cast(num_bytes_allocated_)>=concurrent_start_bytes_)){

......

SirtRef

:

Object>ref(self,obj);

RequestConcurrentGC(self);

}

......

returnobj;

}else{

......

self->ThrowOutOfMemoryError(oss.str().c_str());

returnNULL;

}

}

这个函数定义在文件art/runtime/gc/heap.cc中。

Heap类的成员函数AllocObject首先是要确定要在哪个Space上分配内存。

可以分配内存的Space有三个,分别ZygoteSpace、AllocationSpace和LargeObjectSpace。

不过,ZygoteSpace在还没有划分出AllocationSpace之前,就在ZygoteSpace上分配,而当ZygoteSpace划分出AllocationSpace之后,就只能在AllocationSpace上分配。

从前面一文可以知道,Heap类的成员变量alloc_space_在ZygoteSpace在还没有划分出AllocationSpace之前指向ZygoteSpace,划分之后就指向AllocationSpace。

LargeObjectSpace则始终由Heap类的成员变量large_object_space_指向。

只要满足以下三个条件,就在LargeObjectSpace上分配,否则就在ZygoteSpace或者AllocationSpace上分配:

1.请求分配的内存大于等于Heap类的成员变量large_object_threshold_指定的值。

这个值等于3*kPageSize,即3个页面的大小。

2.已经从ZygoteSpace划分出AllocationSpace,即Heap类的成员变量have_zygote_space_的值等于true。

3.被分配的对象是一个原子类型数组,即byte数组、int数组和boolean数组等。

确定好要在哪个Space上分配内存之后,就可以调用Heap类的成员函数Allocate进行分配了。

如果分配成功,Heap类的成员函数Allocate就返回新分配的对象,保存在变量obj中。

接下来再做三件事情:

1.调用Object类的成员函数SetClass设置新分配对象obj的类型。

2.调用Heap类的成员函数RecordAllocation记录当前的内存分配状况。

3.检查当前已经分配出去的内存是否已经达到由Heap类的成员变量concurrent_start_bytes_设定的阀值。

如果达到,那么就调用Heap类的成员函数RequestConcurrentGC通知GC执行一次并行GC。

关于执行并行GC的阀值,接下来分析ART运行时的垃圾收集过程中再详细分析。

另一方面,如果Heap类的成员函数Allocate分配内存失败,则Heap类的成员函数AllocObject抛出一个OOM异常。

接下来,我们先分析Heap类的成员函数RecordAllocation的实现,接着再分析Heap类的成员函数Allocate的实现。

因为后者的执行流程比较复杂,而前者的执行流程比较简单。

我们先分析容易的,以免打断后面的分析。

Heap类的成员函数RecordAllocation的实现如下所示:

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

inlinevoidHeap:

:

RecordAllocation(size_tsize,mirror:

:

Object*obj){

DCHECK(obj!

=NULL);

DCHECK_GT(size,0u);

num_bytes_allocated_.fetch_add(size);

if(Runtime:

:

Current()->HasStatsEnabled()){

RuntimeStats*thread_stats=Thread:

:

Current()->GetStats();

++thread_stats->allocated_objects;

thread_stats->allocated_bytes+=size;

//TODO:

Updatetheseatomically.

RuntimeStats*global_stats=Runtime:

:

Current()->GetStats();

++global_stats->allocated_objects;

global_stats->allocated_bytes+=size;

}

//ThisissafetodosincetheGCwillneverfreeobjectswhichareneitherintheallocation

//stackorthelivebitmap.

while(!

allocation_stack_->AtomicPushBack(obj)){

CollectGarbageInternal(collector:

:

kGcTypeSticky,kGcCauseForAlloc,false);

}

}

这个函数定义在文件art/runtime/gc/heap.cc中。

Heap类的成员函数RecordAllocation首先是记录当前已经分配的内存字节数以及对象数,接着再将新分配的对象压入到Heap类的成员变量allocation_stack_描述的AllocationStack中去。

后面这一点与Dalvik虚拟机的做法是不一样的。

Dalvik虚拟机直接将新分配出来的对象记录在LiveBitmap中,具体可以参考前面一文。

ART运行时之所以要将新分配的对象压入到AllocationStack中去,是为了以后可以执行StickyGC。

注意,如果不能成功将新分配的对角压入到AllocationStack中,就说明上次GC以来,新分配的对象太多了,因此这时候就需要执行一个StickyGC,将AllocationStack里面的垃圾进行回收,然后再尝试将新分配的对象压入到AllocationStack中,直到成功为止。

接下来我们就重点分析Heap类的成员函数Allocate的实现,以便可以了解新创建对象在堆上分配的具体过程,如下所示:

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

template

inlinemirror:

:

Object*Heap:

:

Allocate(Thread*self,T*space,size_talloc_size,

size_t*bytes_allocated){

......

mirror:

:

Object*ptr=TryToAllocate(self,space,alloc_size,false,bytes_allocated);

if(ptr!

=NULL){

returnptr;

}

returnAllocateInternalWithGc(self,space,alloc_size,bytes_allocated);

}

这个函数定义在文件art/runtime/gc/heap.cc中。

Heap类的成员函数Allocate首先调用成员函数TryToAllocate尝试在不执行GC的情况下进行内存分配。

如果分配失败,再调用成员函数AllocateInternalWithGc进行带GC的内存分配。

Heap类的成员函数Allocate是一个模板函数,不同类型的Space会导致调用不同重载的成员函数TryToAllocate进行不带GC的内存分配。

虽然可以用来分配内存的Space有ZygoteSpace、AllocationSpace和LargeObjectSpace三个,但是前两者的类型是相同的,因此实际上只有两个不同重载版本的成员函数TryToAllocate,它们的实现如下所示:

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

inlinemirror:

:

Object*Heap:

:

TryToAllocate(Thread*self,space:

:

AllocSpace*space,size_talloc_size,

boolgrow,size_t*bytes_allocated){

if(UNLIKELY(IsOutOfMemoryOnAllocation(alloc_size,grow))){

returnNULL;

}

returnspace->Alloc(self,alloc_size,bytes_allocated);

}

//DlMallocSpace-specificversion.

inlinemirror:

:

Object*Heap:

:

TryToAllocate(Thread*self,space:

:

DlMallocSpace*space,size_talloc_size,

boolgrow,size_t*bytes_allocated){

if(UNLIKELY(IsOutOfMem

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 其它模板

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1