[原文出處----------------ART運行時為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/42492621)
ART運行時和Dalvik虛擬機一樣,在堆上為對象分配內存時都要解決內存碎片和內存不足問題。內存碎片問題可以使用dlmalloc技術解決。內存不足問題則通過垃圾回收和在允許范圍內增長堆大小解決。由于垃圾回收會影響程序,因此ART運行時采用力度從小到大的進垃圾回收策略。一旦力度小的垃圾回收執行過后能滿足分配要求,那就不需要進行力度大的垃圾回收了。本文就詳細分析ART運行時在堆上為對象分配內存的過程。
從前面ART運行時Java堆創建過程分析一文可以知道,在ART運行時中,主要用來分配對象的堆空間Zygote Space和Allocation Space的底層使用的都是匿名共享內存,并且通過C庫提供的malloc和free接口來分進行管理。這樣就可以通過dlmalloc技術來盡量解決碎片問題。這一點與我們在前面[Dalvik虛擬機為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/41688319)一文提到的Dalvik虛擬機解決堆內存碎片問題的方法是一樣的。因此,接下來在分析ART運行時為新創建對象分配的過程中,主要會分析它是如何解決內存不足的問題的。
ART運行時為新創建對象分配的過程如圖1所示:

圖1 ART運行時為新創建對象分配內存的過程
對比[Dalvik虛擬機為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/41688319)一文的圖2,可以發現,ART運行時和Dalvik虛擬機為新創建對象分配內存的過程幾乎是一模一樣的,它們的區別僅僅是在于垃圾收集的方式和策略不同。
從前面[Android運行時ART執行類方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/40289405)一文可以知道,ART運行時為從DEX字節碼翻譯得到的Native代碼提供的一個函數調用表中,有一個pAllocObject接口,是用來分配對象的。當ART運行時以Quick模式運行在ARM體系結構時,上述提到的pAllocObject接口由函數art_quick_alloc_object來實現。因此,接下來我們就從函數art_quick_alloc_object的實現開始分析ART運行時為新創建對象分配內存的過程。
函數art_quick_alloc_object的實現如下所示:
~~~
/*
* Called by managed code to allocate an object
*/
.extern artAllocObjectFromCode
ENTRY art_quick_alloc_object
SETUP_REF_ONLY_CALLEE_SAVE_FRAME @ save callee saves in case of GC
mov r2, r9 @ pass Thread::Current
mov r3, sp @ pass SP
bl artAllocObjectFromCode @ (uint32_t type_idx, Method* method, Thread*, SP)
RESTORE_REF_ONLY_CALLEE_SAVE_FRAME
RETURN_IF_RESULT_IS_NON_ZERO
DELIVER_PENDING_EXCEPTION
END art_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的實現如下所示:
~~~
extern "C" mirror::Object* artAllocObjectFromCode(uint32_t type_idx, mirror::ArtMethod* method,
Thread* self, mirror::ArtMethod** sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
FinishCalleeSaveFrameSetup(self, sp, Runtime::kRefsOnly);
return AllocObjectFromCode(type_idx, method, self, false);
}
~~~
這個函數定義在文件art/runtime/entrypoints/quick/quick_alloc_entrypoints.cc中。
函數artAllocObjectFromCode又是通過調用另外一個函數AllocObjectFromCode來分配對象的。不過,在調用函數AllocObjectFromCode之前,函數artAllocObjectFromCode會先調用另外一個函數FinishCalleeSaveFrameSetup在當前調用棧幀中保存一個運行時信息。這個運行時信息描述的是接下來要調用的方法的類型為Runtime::kRefsOnly,也就是由被調用者保存那些不是用來傳遞參數的通用寄存器,即除了r0-r3的其它通用寄存器。
函數AllocObjectFromCode的實現如下所示:
~~~
// Given the context of a calling Method, use its DexCache to resolve a type to a Class. If it
// cannot be resolved, throw an error. If it can, use it to create an instance.
// When verification/compiler hasn't been able to verify access, optionally perform an access
// check.
static inline mirror::Object* AllocObjectFromCode(uint32_t type_idx, mirror::ArtMethod* method,
Thread* self,
bool access_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());
return NULL; // Failure
}
}
if (access_check) {
if (UNLIKELY(!klass->IsInstantiable())) {
ThrowLocation throw_location = self->GetCurrentLocationForThrow();
self->ThrowNewException(throw_location, "Ljava/lang/InstantiationError;",
PrettyDescriptor(klass).c_str());
return NULL; // Failure
}
mirror::Class* referrer = method->GetDeclaringClass();
if (UNLIKELY(!referrer->CanAccess(klass))) {
ThrowIllegalAccessErrorClass(referrer, klass);
return NULL; // Failure
}
}
if (!klass->IsInitialized() &&
!runtime->GetClassLinker()->EnsureInitialized(klass, true, true)) {
DCHECK(self->IsExceptionPending());
return NULL; // Failure
}
return klass->AllocObject(self);
}
~~~
這個函數定義在文件art/runtime/entrypoints/entrypoint_utils.h中。
參數type_idx描述的是要分配的對象的類型,函數AllocObjectFromCode需要將它解析為一個Class對象,以便可以獲得更多的信息進行內存分配。
函數AllocObjectFromCode首先是在當前調用類方法method的Dex Cache中檢查是否已經存在一個與參數type_idx對應的Class對象。如果已經存在,那么就說明參數type_idx描述的對象類型已經被加載和解析過了,因此這時候就可以直接拿來使用。否則的話,就通過調用保存在當前運行時對象內部的一個ClassLinker對象的成員函數ResolveType來對參數type_idx描述的對象類型進行加載和解析。關于Dex Cache的知識,可以參數前面[Android運行時ART執行類方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/40289405)一文,而對象類型(即類)的加載和解析過程可以參考前面[Android運行時ART加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)一文。
得到了要分配的對象的類型klass之后,如果參數access_check的值等于true,那么就對該類型進行檢查,即檢查它是否可以實例化以及是否可以訪問。如果檢查通過,或者不需要檢查,那么接下來還要確保類型klass是已經初始化過了的。前面的檢查都沒有問題之后,最后函數AllocObjectFromCode就調用Class類的成員函數AllocObject來分配一個類型為klass的對象。
Class類的成員函數AllocObject的實現如下所示:
~~~
Object* Class::AllocObject(Thread* self) {
......
return Runtime::Current()->GetHeap()->AllocObject(self, this, this->object_size_);
}
~~~
這個函數定義在文件art/runtime/mirror/class.cc中。
這里我們就終于看到調用ART運行時內部的Heap對象的成員函數AllocObject在堆上分配對象了,其中,要分配的大小保存在當前Class對象的成員變量object_size_中。
Heap類的成員函數AllocObject的實現如下所示:
~~~
mirror::Object* Heap::AllocObject(Thread* self, mirror::Class* c, size_t byte_count) {
......
mirror::Object* obj = NULL;
size_t bytes_allocated = 0;
......
bool large_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<size_t>(num_bytes_allocated_) >= concurrent_start_bytes_)) {
......
SirtRef<mirror::Object> ref(self, obj);
RequestConcurrentGC(self);
}
......
return obj;
} else {
......
self->ThrowOutOfMemoryError(oss.str().c_str());
return NULL;
}
}
~~~
這個函數定義在文件art/runtime/gc/heap.cc中。
Heap類的成員函數AllocObject首先是要確定要在哪個Space上分配內存。可以分配內存的Space有三個,分別Zygote Space、Allocation Space和Large Object Space。不過,Zygote Space在還沒有劃分出Allocation Space之前,就在Zygote Space上分配,而當Zygote Space劃分出Allocation Space之后,就只能在Allocation Space上分配。從前面[ART運行時Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/42379729)一文可以知道,Heap類的成員變量alloc_space_在Zygote Space在還沒有劃分出Allocation Space之前指向Zygote Space,劃分之后就指向Allocation Space。Large Object Space則始終由Heap類的成員變量large_object_space_指向。
只要滿足以下三個條件,就在Large Object Space上分配,否則就在Zygote Space或者Allocation Space上分配:
1. 請求分配的內存大于等于Heap類的成員變量large_object_threshold_指定的值。這個值等于3 * kPageSize,即3個頁面的大小。
2. 已經從Zygote Space劃分出Allocation Space,即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的實現如下所示:
~~~
inline void Heap::RecordAllocation(size_t size, 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: Update these atomically.
RuntimeStats* global_stats = Runtime::Current()->GetStats();
++global_stats->allocated_objects;
global_stats->allocated_bytes += size;
}
// This is safe to do since the GC will never free objects which are neither in the allocation
// stack or the live bitmap.
while (!allocation_stack_->AtomicPushBack(obj)) {
CollectGarbageInternal(collector::kGcTypeSticky, kGcCauseForAlloc, false);
}
}
~~~
這個函數定義在文件art/runtime/gc/heap.cc中。
Heap類的成員函數RecordAllocation首先是記錄當前已經分配的內存字節數以及對象數,接著再將新分配的對象壓入到Heap類的成員變量allocation_stack_描述的Allocation Stack中去。后面這一點與Dalvik虛擬機的做法是不一樣的。Dalvik虛擬機直接將新分配出來的對象記錄在Live Bitmap中,具體可以參考前面[Dalvik虛擬機為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/41688319)一文。ART運行時之所以要將新分配的對象壓入到Allocation Stack中去,是為了以后可以執行Sticky GC。
注意,如果不能成功將新分配的對角壓入到Allocation Stack中,就說明上次GC以來,新分配的對象太多了,因此這時候就需要執行一個Sticky GC,將Allocation Stack里面的垃圾進行回收,然后再嘗試將新分配的對象壓入到Allocation Stack中,直到成功為止。
接下來我們就重點分析Heap類的成員函數Allocate的實現,以便可以了解新創建對象在堆上分配的具體過程,如下所示:
~~~
template <class T>
inline mirror::Object* Heap::Allocate(Thread* self, T* space, size_t alloc_size,
size_t* bytes_allocated) {
......
mirror::Object* ptr = TryToAllocate(self, space, alloc_size, false, bytes_allocated);
if (ptr != NULL) {
return ptr;
}
return AllocateInternalWithGc(self, space, alloc_size, bytes_allocated);
}
~~~
這個函數定義在文件art/runtime/gc/heap.cc中。
Heap類的成員函數Allocate首先調用成員函數TryToAllocate嘗試在不執行GC的情況下進行內存分配。如果分配失敗,再調用成員函數AllocateInternalWithGc進行帶GC的內存分配。
Heap類的成員函數Allocate是一個模板函數,不同類型的Space會導致調用不同重載的成員函數TryToAllocate進行不帶GC的內存分配。雖然可以用來分配內存的Space有Zygote Space、Allocation Space和Large Object Space三個,但是前兩者的類型是相同的,因此實際上只有兩個不同重載版本的成員函數TryToAllocate,它們的實現如下所示:
~~~
inline mirror::Object* Heap::TryToAllocate(Thread* self, space::AllocSpace* space, size_t alloc_size,
bool grow, size_t* bytes_allocated) {
if (UNLIKELY(IsOutOfMemoryOnAllocation(alloc_size, grow))) {
return NULL;
}
return space->Alloc(self, alloc_size, bytes_allocated);
}
// DlMallocSpace-specific version.
inline mirror::Object* Heap::TryToAllocate(Thread* self, space::DlMallocSpace* space, size_t alloc_size,
bool grow, size_t* bytes_allocated) {
if (UNLIKELY(IsOutOfMemoryOnAllocation(alloc_size, grow))) {
return NULL;
}
if (LIKELY(!running_on_valgrind_)) {
return space->AllocNonvirtual(self, alloc_size, bytes_allocated);
} else {
return space->Alloc(self, alloc_size, bytes_allocated);
}
}
~~~
這兩個函數定義在文件art/runtime/gc/heap.cc中。
Heap類兩個重載版本的成員函數TryToAllocate的實現邏輯都幾乎是相同的,首先是調用另外一個成員函數IsOutOfMemoryOnAllocation判斷分配請求的內存后是否會超過堆的大小限制。如果超過,則分配失敗;否則的話再在指定的Space進行內存分配。
Heap類的成員函數IsOutOfMemoryOnAllocation的實現如下所示:
~~~
inline bool Heap::IsOutOfMemoryOnAllocation(size_t alloc_size, bool grow) {
size_t new_footprint = num_bytes_allocated_ + alloc_size;
if (UNLIKELY(new_footprint > max_allowed_footprint_)) {
if (UNLIKELY(new_footprint > growth_limit_)) {
return true;
}
if (!concurrent_gc_) {
if (!grow) {
return true;
} else {
max_allowed_footprint_ = new_footprint;
}
}
}
return false;
}
~~~
這個函數定義在文件art/runtime/gc/heap.cc中。
Heap類的成員變量num_bytes_allocated_描述的是目前已經分配出去的內存字節數,成員變量max_allowed_footprint_描述的是目前堆可分配的最大內存字節數,成員變量growth_limit_描述的是目前堆允許增長到的最大內存字節數。這里需要注意的一點是,max_allowed_footprint_是Heap類施加的一個限制,不會對各個Space實際可分配的最大內存字節數產生影響,并且各個Space在創建的時候,已經把自己可分配的最大內存數設置為允許使用的最大內存字節數的。
如果目前堆已經分配出去的內存字節數再加上請求分配的內存字節數new_footprint小于等于目前堆可分配的最大內存字節數max_allowed_footprint_,那么分配出請求的內存字節數之后不會造成OOM,因此Heap類的成員函數IsOutOfMemoryOnAllocation就返回false。
另一方面,如果目前堆已經分配出去的內存字節數再加上請求分配的內存字節數new_footprint大于目前堆可分配的最大內存字節數max_allowed_footprint_,并且也大于目前堆允許增長到的最大內存字節數growth_limit_,那么分配出請求的內存字節數之后造成OOM,因此Heap類的成員函數IsOutOfMemoryOnAllocation就返回true。
剩下另外一種情況,目前堆已經分配出去的內存字節數再加上請求分配的內存字節數new_footprint大于目前堆可分配的最大內存字節數max_allowed_footprint_,但是小于等于目前堆允許增長到的最大內存字節數growth_limit_,這時候就要看情況會不會出現OOM了。如果ART運行時運行在非并行GC的模式中,即Heap類的成員變量concurrent_gc_等于false,那么取決于允不允許增長堆的大小,即參數grow的值。如果不允許,那么Heap類的成員函數IsOutOfMemoryOnAllocation就返回true,表示當前請求的分配會造成OOM。如果允許,那么Heap類的成員函數IsOutOfMemoryOnAllocation就會修改目前堆可分配的最大內存字節數max_allowed_footprint_,并且返回false,表示允許當前請求的分配。這意味著,在非并行GC運行模式中,在分配內存過程中遇到內存不足,并且當前可分配內存還未達到增長上限時,要等到執行完成一次非并行GC后,才能成功分配到內存,因為每次執行完成GC之后,都會按照預先設置的堆目標利用率來增長堆的大小。
另一方面,如果ART運行時運行在并行GC的模式中,那么只要前堆已經分配出去的內存字節數再加上請求分配的內存字節數new_footprint不地超過目前堆允許增長到的最大內存字節數growth_limit_,那么就不管允不允許增長堆的大小,都認為不會發生OOM,因此Heap類的成員函數IsOutOfMemoryOnAllocation就返回false。這意味著,在并行GC運行模式中,在分配內存過程中遇到內存不足,并且當前可分配內存還未達到增長上限時,無非等到執行并行GC后,就有可能成功分配到內存,因為實際執行內存分配的Space可分配的最大內存字節數是足夠的。
回到前面Heap類的成員函數TryToAllocate中,從前面[ART運行時Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/42379729)一文可以知道,對于Large Object Space版本的成員函數TryToAllocate,調用的是LargeObjectMapSpace類的成員函數Alloc進行內存分配,而對于Zygote Space或者Allocation Space版本的成員函數TryToAllocate,如果成員變量running_on_valgrind_的值等于true,就調用ValgrindDlMallocSpace類的成員函數AllocNonvirtual進行內存分配,否則就調用DlMallocSpace類的成員函數Alloc進行內存分配。我們假設Heap類的成員變量running_on_valgrind_的值等于false,因此接下來我們主要分析LargeObjectMapSpace類的成員函數Alloc和DlMallocSpace類的成員函數Alloc的實現。
LargeObjectMapSpace類的成員函數Alloc的實現如下所示:
~~~
mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated) {
MemMap* mem_map = MemMap::MapAnonymous("large object space allocation", NULL, num_bytes,
PROT_READ | PROT_WRITE);
if (mem_map == NULL) {
return NULL;
}
MutexLock mu(self, lock_);
mirror::Object* obj = reinterpret_cast<mirror::Object*>(mem_map->Begin());
large_objects_.push_back(obj);
mem_maps_.Put(obj, mem_map);
size_t allocation_size = mem_map->Size();
DCHECK(bytes_allocated != NULL);
*bytes_allocated = allocation_size;
num_bytes_allocated_ += allocation_size;
total_bytes_allocated_ += allocation_size;
++num_objects_allocated_;
++total_objects_allocated_;
return obj;
}
~~~
這個函數定義在文件art/runtime/gc/space/large_object_space.cc中。
從這里就可以看到,Large Object Map Space分配內存的邏輯是很簡單的,直接就是調用MemMap類的靜態成員函數MapAnonymous創建一塊指定大小的匿名內存,然后再將該匿名共享內存添加到成員變量large_objects_描述的一個向量中去,最后更新內部的各個統計數據。
DlMallocSpace類的成員函數Alloc的實現如下所示:
~~~
mirror::Object* DlMallocSpace::Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated) {
return AllocNonvirtual(self, num_bytes, bytes_allocated);
}
~~~
這個函數定義在文件art/runtime/gc/space/dlmalloc_space.cc中。
DlMallocSpace類的成員函數Alloc調用另外一個成員函數AllocNonvirtual來進行內存分配,后者的實現如下所示:
~~~
inline mirror::Object* DlMallocSpace::AllocNonvirtual(Thread* self, size_t num_bytes,
size_t* bytes_allocated) {
mirror::Object* obj;
{
MutexLock mu(self, lock_);
obj = AllocWithoutGrowthLocked(num_bytes, bytes_allocated);
}
if (obj != NULL) {
// Zero freshly allocated memory, done while not holding the space's lock.
memset(obj, 0, num_bytes);
}
return obj;
}
~~~
這個函數定義在文件art/runtime/gc/space/dlmalloc_space-inl.h中。
DlMallocSpace類的成員函數AllocNonvirtual首先是調用另外一個成員函數AllocWithoutGrowthLocked在不增長Space的大小的前提下進行內存分配,分配成功之后再調用函數memset對分配出來的內存進行清空,最后將分配出來的內存返回給調用者。
DlMallocSpace類的成員函數AllocWithoutGrowthLocked的實現如下所示:
~~~
inline mirror::Object* DlMallocSpace::AllocWithoutGrowthLocked(size_t num_bytes, size_t* bytes_allocated) {
mirror::Object* result = reinterpret_cast<mirror::Object*>(mspace_malloc(mspace_, num_bytes));
if (result != NULL) {
if (kDebugSpaces) {
CHECK(Contains(result)) << "Allocation (" << reinterpret_cast<void*>(result)
<< ") not in bounds of allocation space " << *this;
}
size_t allocation_size = AllocationSizeNonvirtual(result);
DCHECK(bytes_allocated != NULL);
*bytes_allocated = allocation_size;
num_bytes_allocated_ += allocation_size;
total_bytes_allocated_ += allocation_size;
++total_objects_allocated_;
++num_objects_allocated_;
}
return result;
}
~~~
這個函數定義在文件art/runtime/gc/space/dlmalloc_space-inl.h中。
從前面[ART運行時Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/42379729)一文可以知道,DlMallocSpace底層使用的匿名共享內存塊被封裝成一個mspace對象,并且保存在成員變量mspace_中,因此這里就可以直接調用C庫提供的mspace_malloc接口進行內存分配。使用mspace_malloc分配的內存會自動被清空,因此這里不用再手動清空。DlMallocSpace類的成員函數AllocWithoutGrowthLocked在將分配出來的內存返回給調用者之前,同樣是會更新內部的各個統計數據。
回到前面Heap類的成員函數Allocate中,在調用成員函數TryToAllocate不能成功分配指定大小的內存塊之后,接下來就繼續調用成員函數AllocateInternalWithGc執行帶GC的內存分配,它的實現如下所示:
~~~
mirror::Object* Heap::AllocateInternalWithGc(Thread* self, space::AllocSpace* space,
size_t alloc_size, size_t* bytes_allocated) {
mirror::Object* ptr;
// The allocation failed. If the GC is running, block until it completes, and then retry the
// allocation.
collector::GcType last_gc = WaitForConcurrentGcToComplete(self);
if (last_gc != collector::kGcTypeNone) {
// A GC was in progress and we blocked, retry allocation now that memory has been freed.
ptr = TryToAllocate(self, space, alloc_size, false, bytes_allocated);
if (ptr != NULL) {
return ptr;
}
}
// Loop through our different Gc types and try to Gc until we get enough free memory.
for (size_t i = static_cast<size_t>(last_gc) + 1;
i < static_cast<size_t>(collector::kGcTypeMax); ++i) {
bool run_gc = false;
collector::GcType gc_type = static_cast<collector::GcType>(i);
switch (gc_type) {
case collector::kGcTypeSticky: {
const size_t alloc_space_size = alloc_space_->Size();
run_gc = alloc_space_size > min_alloc_space_size_for_sticky_gc_ &&
alloc_space_->Capacity() - alloc_space_size >= min_remaining_space_for_sticky_gc_;
break;
}
case collector::kGcTypePartial:
run_gc = have_zygote_space_;
break;
case collector::kGcTypeFull:
run_gc = true;
break;
default:
break;
}
if (run_gc) {
// If we actually ran a different type of Gc than requested, we can skip the index forwards.
collector::GcType gc_type_ran = CollectGarbageInternal(gc_type, kGcCauseForAlloc, false);
DCHECK_GE(static_cast<size_t>(gc_type_ran), i);
i = static_cast<size_t>(gc_type_ran);
// Did we free sufficient memory for the allocation to succeed?
ptr = TryToAllocate(self, space, alloc_size, false, bytes_allocated);
if (ptr != NULL) {
return ptr;
}
}
}
// Allocations have failed after GCs; this is an exceptional state.
// Try harder, growing the heap if necessary.
ptr = TryToAllocate(self, space, alloc_size, true, bytes_allocated);
if (ptr != NULL) {
return ptr;
}
......
// We don't need a WaitForConcurrentGcToComplete here either.
CollectGarbageInternal(collector::kGcTypeFull, kGcCauseForAlloc, true);
return TryToAllocate(self, space, alloc_size, true, bytes_allocated);
}
~~~
這個函數定義在文件art/runtime/gc/heap.cc中。
Heap類的成員函數AllocateInternalWithGc主要是通過垃圾回收來滿足請求分配的內存,它的執行邏輯如下所示:
1. 調用Heap類的成員函數WaitForConcurrentGcToComplete檢查是否有并行GC正在執行。如果有的話,就等待其執行完成,并且得到它的類型last_gc。如果last_gc如果不等于collector::kGcTypeNone,就表示有并行GC并且已經執行完成,因此就可以調用Heap類的成員函數TryToAllocate在不增長當前堆大小的前提下再次嘗試分配請求的內存了。如果分配成功,則返回得到的內存起始地址給調用者。否則的話,繼續往下執行。
2. 依次執行kGcTypeSticky、kGcTypePartial和kGcTypeFull三種類型的GC。每次GC執行完畢,都嘗試調用Heap類的成員函數TryToAllocate在不增長當前堆大小的前提下再次嘗試分配請求的內存。如果分配內存成功,則返回得到的內存起始地址給調用者,并且不再執行下一種類型的GC。
這里需要注意的一點是,kGcTypeSticky、kGcTypePartial和kGcTypeFull三種類型的GC的垃圾回收力度是依次加強:kGcTypeSticky只回收上次GC后在Allocation Space中新分配的垃圾對象;kGcTypePartial只回收Allocation Space的垃圾對象;kGcTypeFull同時回收Zygote Space和Allocation Space的垃圾對象。通過這種策略,就有可能以最小代價解決分配對象時遇到的內在不足問題。不過,對于類型為kGcTypeSticky和kGcTypePartial的GC,它們的執行有前提條件的。
類型為kGcTypeSticky的GC的執行代碼雖然是最小的,但是它能夠回收的垃圾也是最小的。如果回收的垃圾不足于滿足請求分配的內存,那就相當于做了一次無用功了。因此,執行類型為kGcTypeSticky的GC需要滿足兩個條件。第一個條件是上次GC后在Allocation Space上分配的內存要達到一定的閥值,這樣才有比較大的概率回收到較多的內存。第二個條件Allocation Space剩余的未分配內存要達到一定的閥值,這樣可以保證在回收得到較少內存時,也有比較大的概率滿足請求分配的內存。前一個閥值定義在Heap類的成員變量min_alloc_space_size_for_sticky_gc_中,它的值設置為2M,而上次GC以來分配的內存通過當前Allocation Space的大小估算得到,即通過調用Heap類的成員變量alloc_space_指向的一個DlMallocSpace對象的成員函數Size獲得。后一個閥值定義在Heap類的成員變量min_remaining_space_for_sticky_gc_中,它的值設置為1M,而Allocation Space剩余的未分配內存可以用Allocation Space的總大小減去當前Allocation Space的大小得到。通過調用Heap類的成員變量alloc_space_指向的一個DlMallocSpace對象的成員函數Capacity獲得其總大小。
類型為kGcTypePartial的GC的執行前提是已經從Zygote Space中劃分出Allocation Space。從前面[ART運行時Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/42379729)一文可以知道,當Heap類的成員變量have_zygote_space_的值等于true時,就表明已經從Zygote Space中劃分出Allocation Space了。因此,在這種情況下,就可以執行類型為kGcTypePartial的GC了。
每一種類型的GC都是通過調用Heap類的成員函數CollectGarbageInternal來執行。注意這時候調用Heap類的成員函數CollectGarbageInternal傳遞的第三個參數為false,表示不對那些只被軟引用對象引用的對象進行回收。如果上述的三種類型的GC執行完畢,還是不能滿足分配請求的內存,則繼續往下執行。
3. 經過前面三種類型的GC后還是不能成功分配到內存,那就說明能夠回收的內存還是太小了,因此,這時候只能通過在允許范圍內增長堆的大小來滿足內存分配請求了。前面分析Heap類的成員函數TryToAllocate時,將第四個參數設置為true,即可在允許范圍內增長堆大小的前提下進行內存分配。如果在允許范圍內增長了堆的大小還是不能成功分配到請求的內存,那就只能出最后的一個大招了。
4. 最后的大招是首先執行一個類型為kGcTypeFull的、要求回收那些只被軟引用對象引用的對象的GC,接著再在允許范圍內增長堆大小的前提下嘗試分配內存。這一次如果還是失敗,那就真的是內存不足了。
至此,我們就對ART運行時在堆上為新創建對象分配內存的過程分析完成了。從中我們就可以看到,ART運行時面臨的最大挑戰就是內存不足問題,它要通過在允許范圍內增長堆大小以及垃圾回收兩個手段來解決。其中,垃圾回收會對程序造成影響,因此在執行垃圾回收時,使用的力度要從小到大。
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析