ART运行时Compacting GC为新创建对象分配内存的过程分析.docx
- 文档编号:27314443
- 上传时间:2023-06-29
- 格式:DOCX
- 页数:58
- 大小:208.87KB
ART运行时Compacting GC为新创建对象分配内存的过程分析.docx
《ART运行时Compacting GC为新创建对象分配内存的过程分析.docx》由会员分享,可在线阅读,更多相关《ART运行时Compacting GC为新创建对象分配内存的过程分析.docx(58页珍藏版)》请在冰豆网上搜索。
ART运行时CompactingGC为新创建对象分配内存的过程分析
ART运行时CompactingGC为新创建对象分配内存的过程分析
在引进CompactingGC后,ART运行时优化了堆内存分配过程。
最显著特点是为每个ART运行时线程增加局部分配缓冲区(TheadLocalAllocationBuffer)和在OOM前进行一次同构空间压缩(HomogeneousSpaceCompact)。
前者可提高堆内存分配效率,后者可解决内存碎片问题。
本文就对ART运行时引进CompactingGC后的堆内存分配过程进行分析。
从接口层面上看,除了提供常规的对象分配接口AllocObject,ART运行时的堆还提供了一个专门用于分配非移动对象的接口AllocNonMovableObject,如图1所示:
非移动对象指的是保存在前面一篇文章提到的Non-MovingSpace的对象,主要包括那些在类加载过程中创建的类对象(Class)、类方法对象(ArtMethod)和类成员变量对象(ArtField)等,以及那些在经历过若干次GenerationalSemi-SpaceGC之后仍然存活的对象。
前者是通过AllocNonMovableObject接口分配的,而后者是在执行GenerationalSemi-SpaceGC过程移动过去的。
本文主要关注通过AllocNonMovableObject接口分配的非移动对象。
无论是通过AllocObject接口分配对象,还是通过AllocNonMovableObject接口分配对象,最后都统一调用了另外一个接口AllocObjectWithAllocator进行具体的分配过程,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classHeap{
public:
......
//Allocatesandinitializesstorageforanobjectinstance.
template
mirror:
:
Object*AllocObject(Thread*self,mirror:
:
Class*klass,size_tnum_bytes,
constPreFenceVisitor&pre_fence_visitor)
SHARED_LOCKS_REQUIRED(Locks:
:
mutator_lock_){
returnAllocObjectWithAllocator
GetCurrentAllocator(),
pre_fence_visitor);
}
template
mirror:
:
Object*AllocNonMovableObject(Thread*self,mirror:
:
Class*klass,size_tnum_bytes,
constPreFenceVisitor&pre_fence_visitor)
SHARED_LOCKS_REQUIRED(Locks:
:
mutator_lock_){
returnAllocObjectWithAllocator
GetCurrentNonMovingAllocator(),
pre_fence_visitor);
}
template
ALWAYS_INLINEmirror:
:
Object*AllocObjectWithAllocator(
Thread*self,mirror:
:
Class*klass,size_tbyte_count,AllocatorTypeallocator,
constPreFenceVisitor&pre_fence_visitor)
SHARED_LOCKS_REQUIRED(Locks:
:
mutator_lock_);
AllocatorTypeGetCurrentAllocator()const{
returncurrent_allocator_;
}
AllocatorTypeGetCurrentNonMovingAllocator()const{
returncurrent_non_moving_allocator_;
}
......
private:
......
//Allocatortype.
AllocatorTypecurrent_allocator_;
constAllocatorTypecurrent_non_moving_allocator_;
......
};
这五个函数定义在文件art/runtime/gc/heap.h
在Heap类的成员函数AllocObject和AllocNonMovableObject中,参数self描述的是当前线程,klass描述的是要分配的对象所属的类型,参数num_bytes描述的是要分配的对象的大小,最后一个参数pre_fence_visitor是一个回调函数,用来在分配对象完成后在当前执行路径中执行初始化操作,例如分配完成一个数组对象,通过该回调函数立即设置数组的大小,这样就可以保证数组对象的完整性和一致性,避免多线程环境下通过加锁来完成相同的操作。
Heap类的成员函数AllocObjectWithAllocator需要另外一个额外的类型为AllocatorType的参数来描述分配器的类型,也就是描述要在哪个空间分配对象。
AllocatorType是一个枚举类型,它的定义如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
//Differenttypesofallocators.
enumAllocatorType{
kAllocatorTypeBumpPointer,//UseBumpPointerallocator,hasentrypoints.
kAllocatorTypeTLAB,//UseTLABallocator,hasentrypoints.
kAllocatorTypeRosAlloc,//UseRosAllocallocator,hasentrypoints.
kAllocatorTypeDlMalloc,//Usedlmallocallocator,hasentrypoints.
kAllocatorTypeNonMoving,//Specialallocatorfornonmovingobjects,doesn'thaveentrypoints.
kAllocatorTypeLOS,//Largeobjectspace,alsodoesn'thaveentrypoints.
};
这个枚举类型定义在文件/art/runtime/gc/allocator_type.h。
AllocatorType一共有六个值,它们的含义如下所示:
kAllocatorTypeBumpPointer:
表示在BumpPointerSpace中分配对象。
kAllocatorTypeTLAB:
表示要在由BumpPointerSpace提供的线程局部分配缓冲区中分配对象。
kAllocatorTypeRosAlloc:
表示要在RosAllocSpace分配对象。
kAllocatorTypeDlMalloc:
表示要在DlMallocSpace分配对象。
kAllocatorTypeNonMoving:
表示要在NonMovingSpace分配对象。
kAllocatorTypeLOS:
表示要在LargeObjectSpace分配对象。
Heap类的成员函数AllocObject和AllocNonMovableObject使用的分配器类型分别是由成员变量current_allocator_和current_non_moving_allocator_决定的。
前者的值与当前使用的GC类型有关。
当GC类型发生变化时,就会调用Heap类的成员函数ChangeCollector来修改当前使用的GC,同时也会调用另外一个成员函数ChangeAllocator来修改Heap类的成员变量current_allocator_的值。
由于ART运行时只有一个Non-MovingSpace,因此后者的值就固定为kAllocatorTypeNonMoving。
Heap类的成员函数ChangeCollector的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidHeap:
:
ChangeCollector(CollectorTypecollector_type){
//TODO:
Onlydothiswithallmutatorssuspendedtoavoidraces.
if(collector_type!
=collector_type_){
......
collector_type_=collector_type;
gc_plan_.clear();
switch(collector_type_){
casekCollectorTypeCC:
//Fall-through.
casekCollectorTypeMC:
//Fall-through.
casekCollectorTypeSS:
//Fall-through.
casekCollectorTypeGSS:
{
gc_plan_.push_back(collector:
:
kGcTypeFull);
if(use_tlab_){
ChangeAllocator(kAllocatorTypeTLAB);
}else{
ChangeAllocator(kAllocatorTypeBumpPointer);
}
break;
}
casekCollectorTypeMS:
{
gc_plan_.push_back(collector:
:
kGcTypeSticky);
gc_plan_.push_back(collector:
:
kGcTypePartial);
gc_plan_.push_back(collector:
:
kGcTypeFull);
ChangeAllocator(kUseRosAlloc?
kAllocatorTypeRosAlloc:
kAllocatorTypeDlMalloc);
break;
}
casekCollectorTypeCMS:
{
gc_plan_.push_back(collector:
:
kGcTypeSticky);
gc_plan_.push_back(collector:
:
kGcTypePartial);
gc_plan_.push_back(collector:
:
kGcTypeFull);
ChangeAllocator(kUseRosAlloc?
kAllocatorTypeRosAlloc:
kAllocatorTypeDlMalloc);
break;
}
default:
{
LOG(FATAL)<<"Unimplemented";
}
}
......
}
}
这个函数定义在文件ime/gc/heap.cc中。
从这里我们就可以看到,对于CompactingGC,它们使用的分配器类型只可能为kAllocatorTypeTLAB或者kAllocatorTypeBumpPointer,取决定Heap类的成员变量use_tlab_的值。
Heap类的成员变量use_tlab_的值默认为false,但是可以通过ART运行时启动选项-XX:
UseTLAB来设置为true。
对于Mark-SweepGC来说,它们使用的分配器类型只可能为kAllocatorTypeRosAlloc或者kAllocatorTypeDlMalloc,取决于常量kUseRosAlloc的值。
此外,我们还可以看到,根据当前使用的GC不同,Heap类的成员变量gc_plan_会被设置为不同的值,用来表示在分配对象过程中遇到内存不足时,应该执行的GC粒度。
对于CompactingGC来说,只有一种GC粒度可执行,那就是kGcTypeFull,实际上就是说对BumpPointerSpace的所有不可达对象进行回收。
对于Mark-SweepGC来说,有三种GC粒度可执行,分别是kGcTypeSticky、kGcTypePartial和kGcTypeFull。
这三者的含义可以参考前面一文。
后面我们继续对象分配过程时,也可以看到Heap类的成员变量gc_plan_的用途。
Heap类的成员函数ChangeAllocator的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidHeap:
:
ChangeAllocator(AllocatorTypeallocator){
if(current_allocator_!
=allocator){
......
current_allocator_=allocator;
MutexLockmu(nullptr,*Locks:
:
runtime_shutdown_lock_);
SetQuickAllocEntryPointsAllocator(current_allocator_);
......
}
}
这个函数定义在文件ime/gc/heap.cc中。
Heap类的成员函数ChangeAllocator除了设置成员变量current_allocator_的值之外,还会调用函数SetQuickAllocEntryPointsAllocator来修改提供给NativeCode的用来分配对象的入口点函数,以便NativeCode可以在ART运行时切换GC时使用正常的接口来分配对象。
这里所谓的NativeCode,就是APK在安装时通过翻译DEX字节码得到的本地机器指令。
了解了分配器的类型之后,接下来我们就继续分析Heap类的成员函数AllocObjectWithAllocator的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
template
inlinemirror:
:
Object*Heap:
:
AllocObjectWithAllocator(Thread*self,mirror:
:
Class*klass,
size_tbyte_count,AllocatorTypeallocator,
constPreFenceVisitor&pre_fence_visitor){
......
if(kCheckLargeObject&&UNLIKELY(ShouldAllocLargeObject(klass,byte_count))){
returnAllocLargeObject
pre_fence_visitor);
}
mirror:
:
Object*obj;
......
if(allocator==kAllocatorTypeTLAB){
byte_count=RoundUp(byte_count,space:
:
BumpPointerSpace:
:
kAlignment);
}
if(allocator==kAllocatorTypeTLAB&&byte_count<=self->TlabSize()){
obj=self->AllocTlab(byte_count);
......
obj->SetClass(klass);
......
pre_fence_visitor(obj,usable_size);
......
}else{
obj=TryToAllocate
&usable_size);
if(UNLIKELY(obj==nullptr)){
boolis_current_allocator=allocator==GetCurrentAllocator();
obj=AllocateInternalWithGc(self,allocator,byte_count,&bytes_allocated,&usable_size,
&klass);
if(obj==nullptr){
boolafter_is_current_allocator=allocator==GetCurrentAllocator();
//Ifthereisapendingexception,failtheallocationrightawaysincethenextone
//couldcauseOOMandaborttheruntime.
if(!
self->IsExceptionPending()&&is_current_allocator&&!
after_is_current_allocator){
//Iftheallocatorchanged,weneedtorestarttheallocation.
returnAllocObject
}
returnnullptr;
}
}
......
obj->SetClass(klass);
......
pre_fence_visitor(obj,usable_size);
......
}
......
if(AllocatorHasAllocationStack(allocator)){
PushOnAllocationStack(self,&obj);
}
......
if(AllocatorMayHaveConcurrentGC(allocator)&&IsGcConcurrent()){
CheckConcurrentGC(self,new_num_bytes_allocated,&obj);
}
......
returnobj;
}
这个函数定义在文件art/runtime/gc/heap-inl.h中。
Heap类的成员函数AllocObjectWithAllocator分配对象的主要逻辑如图2所示:
首先,如果模板参数kCheckLargeObject等于true,并且要分配的是一个原子类型数组,且该为数组的大小大于预先设置的值,那么忽略掉参数allocator,而是调用Heap类的另外一个成员函数AllocLargeObject直接在LargeObjectSpace中分配内存。
后一个条件是通过调用Heap类的成员函数ShouldAllocLargeObject来判断是否满足的,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
inlineboolHeap:
:
ShouldAllocLargeObject(mirror:
:
Class*c,size_tbyte_count)const{
//Weneedtohaveazygotespaceorelseournewlyallocatedlargeobjectcanendupinthe
//Zygoteresultinginitbeingprematurelyfreed.
//Wecanonlydothisforprimitiveobjectssincelargeobjectswillnotbewithinthecardtable
//range.ThisalsomeansthatwerelyonSetClassnotdirtyingtheobject'scard.
returnbyte_count>=large_object_threshold_&&c->IsPrimitiveArray();
}
这个函数定义在文件art/runtime/gc/heap-inl.h中。
Heap类的成员变量large_object_threshold_初始化为kDefaultLargeObjectThreshold,后者又定义为3个内存页大小。
也就是说,当分配的原子类型数组大小大于等于3个内存页时,就在LargeObjectSpace中进行分配。
回到Heap类的成员AllocObjectWithAllocator中,如果指定了要在当前ART运行时线程的TLAB中分配对象,并且这时候当前ART运行时线程的TLAB的剩余大小大于请求分配的对象大小,那么就直接在当前线程的TLAB中分配。
ART运行时线程的TLAB实际上是来自于BumpPointerSpace上的,后面我们就可以看到这一点。
如果上面的条件都不成立,接下来就调用Heap类的成员函数TryToAllocate来进行分配了。
Heap类的成员函数TryToAllocate会根据参数allocator,在指定的Space分配内存,同时会根据第二个模板参数来决定
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- ART运行时Compacting GC为新创建对象分配内存的过程分析 ART 运行 Compacting GC 创建 对象 分配 内存 过程 分析