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