[原文出處--------------------ART運行時Compacting GC為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/44910271)
在引進Compacting GC后,ART運行時優化了堆內存分配過程。最顯著特點是為每個ART運行時線程增加局部分配緩沖區(Thead Local Allocation Buffer)和在OOM前進行一次同構空間壓縮(Homogeneous Space Compact)。前者可提高堆內存分配效率,后者可解決內存碎片問題。本文就對ART運行時引進Compacting GC后的堆內存分配過程進行分析。
從接口層面上看,除了提供常規的對象分配接口AllocObject,ART運行時的堆還提供了一個專門用于分配非移動對象的接口AllocNonMovableObject,如圖1所示:

圖1 ART運行時堆提供的對象分配接口
非移動對象指的是保存在前面ART運行時Compacting GC堆創建過程分析一篇文章提到的Non-Moving Space的對象,主要包括那些在類加載過程中創建的類對象(Class)、類方法對象(ArtMethod)和類成員變量對象(ArtField)等,以及那些在經歷過若干次Generational Semi-Space GC之后仍然存活的對象。前者是通過AllocNonMovableObject接口分配的,而后者是在執行Generational Semi-Space GC過程移動過去的。本文主要關注通過AllocNonMovableObject接口分配的非移動對象。
無論是通過AllocObject接口分配對象,還是通過AllocNonMovableObject接口分配對象,最后都統一調用了另外一個接口AllocObjectWithAllocator進行具體的分配過程,如下所示:
~~~
class Heap {
public:
......
// Allocates and initializes storage for an object instance.
template <bool kInstrumented, typename PreFenceVisitor>
mirror::Object* AllocObject(Thread* self, mirror::Class* klass, size_t num_bytes,
const PreFenceVisitor& pre_fence_visitor)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
return AllocObjectWithAllocator<kInstrumented, true>(self, klass, num_bytes,
GetCurrentAllocator(),
pre_fence_visitor);
}
template <bool kInstrumented, typename PreFenceVisitor>
mirror::Object* AllocNonMovableObject(Thread* self, mirror::Class* klass, size_t num_bytes,
const PreFenceVisitor& pre_fence_visitor)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
return AllocObjectWithAllocator<kInstrumented, true>(self, klass, num_bytes,
GetCurrentNonMovingAllocator(),
pre_fence_visitor);
}
template <bool kInstrumented, bool kCheckLargeObject, typename PreFenceVisitor>
ALWAYS_INLINE mirror::Object* AllocObjectWithAllocator(
Thread* self, mirror::Class* klass, size_t byte_count, AllocatorType allocator,
const PreFenceVisitor& pre_fence_visitor)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
AllocatorType GetCurrentAllocator() const {
return current_allocator_;
}
AllocatorType GetCurrentNonMovingAllocator() const {
return current_non_moving_allocator_;
}
......
private:
......
// Allocator type.
AllocatorType current_allocator_;
const AllocatorType current_non_moving_allocator_;
......
};
~~~
這五個函數定義在文件art/runtime/gc/heap.h
在Heap類的成員函數AllocObject和AllocNonMovableObject中,參數self描述的是當前線程,klass描述的是要分配的對象所屬的類型,參數num_bytes描述的是要分配的對象的大小,最后一個參數pre_fence_visitor是一個回調函數,用來在分配對象完成后在當前執行路徑中執行初始化操作,例如分配完成一個數組對象,通過該回調函數立即設置數組的大小,這樣就可以保證數組對象的完整性和一致性,避免多線程環境下通過加鎖來完成相同的操作。
Heap類的成員函數AllocObjectWithAllocator需要另外一個額外的類型為AllocatorType的參數來描述分配器的類型,也就是描述要在哪個空間分配對象。AllocatorType是一個枚舉類型,它的定義如下所示:
~~~
// Different types of allocators.
enum AllocatorType {
kAllocatorTypeBumpPointer, // Use BumpPointer allocator, has entrypoints.
kAllocatorTypeTLAB, // Use TLAB allocator, has entrypoints.
kAllocatorTypeRosAlloc, // Use RosAlloc allocator, has entrypoints.
kAllocatorTypeDlMalloc, // Use dlmalloc allocator, has entrypoints.
kAllocatorTypeNonMoving, // Special allocator for non moving objects, doesn't have entrypoints.
kAllocatorTypeLOS, // Large object space, also doesn't have entrypoints.
};
~~~
這個枚舉類型定義在文件/art/runtime/gc/allocator_type.h。
AllocatorType一共有六個值,它們的含義如下所示:
* kAllocatorTypeBumpPointer:表示在Bump Pointer Space中分配對象。
* kAllocatorTypeTLAB:表示要在由Bump Pointer Space提供的線程局部分配緩沖區中分配對象。
* kAllocatorTypeRosAlloc:表示要在Ros Alloc Space分配對象。
* kAllocatorTypeDlMalloc:表示要在Dl Malloc Space分配對象。
* kAllocatorTypeNonMoving:表示要在Non Moving Space分配對象。
* kAllocatorTypeLOS:表示要在Large Object Space分配對象。
Heap類的成員函數AllocObject和AllocNonMovableObject使用的分配器類型分別是由成員變量current_allocator_和current_non_moving_allocator_決定的。前者的值與當前使用的GC類型有關。當GC類型發生變化時,就會調用Heap類的成員函數ChangeCollector來修改當前使用的GC,同時也會調用另外一個成員函數ChangeAllocator來修改Heap類的成員變量current_allocator_的值。由于ART運行時只有一個Non-Moving Space,因此后者的值就固定為kAllocatorTypeNonMoving。
Heap類的成員函數ChangeCollector的實現如下所示:
~~~
void Heap::ChangeCollector(CollectorType collector_type) {
// TODO: Only do this with all mutators suspended to avoid races.
if (collector_type != collector_type_) {
......
collector_type_ = collector_type;
gc_plan_.clear();
switch (collector_type_) {
case kCollectorTypeCC: // Fall-through.
case kCollectorTypeMC: // Fall-through.
case kCollectorTypeSS: // Fall-through.
case kCollectorTypeGSS: {
gc_plan_.push_back(collector::kGcTypeFull);
if (use_tlab_) {
ChangeAllocator(kAllocatorTypeTLAB);
} else {
ChangeAllocator(kAllocatorTypeBumpPointer);
}
break;
}
case kCollectorTypeMS: {
gc_plan_.push_back(collector::kGcTypeSticky);
gc_plan_.push_back(collector::kGcTypePartial);
gc_plan_.push_back(collector::kGcTypeFull);
ChangeAllocator(kUseRosAlloc ? kAllocatorTypeRosAlloc : kAllocatorTypeDlMalloc);
break;
}
case kCollectorTypeCMS: {
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中。
從這里我們就可以看到,對于Compacting GC,它們使用的分配器類型只可能為kAllocatorTypeTLAB或者kAllocatorTypeBumpPointer,取決定Heap類的成員變量use_tlab_的值。Heap類的成員變量use_tlab_的值默認為false,但是可以通過ART運行時啟動選項-XX:UseTLAB來設置為true。對于Mark-Sweep GC來說,它們使用的分配器類型只可能為kAllocatorTypeRosAlloc或者kAllocatorTypeDlMalloc,取決于常量kUseRosAlloc的值。
此外,我們還可以看到,根據當前使用的GC不同,Heap類的成員變量gc_plan_會被設置為不同的值,用來表示在分配對象過程中遇到內存不足時,應該執行的GC粒度。對于Compacting GC來說,只有一種GC粒度可執行,那就是kGcTypeFull,實際上就是說對Bump Pointer Space的所有不可達對象進行回收。對于Mark-Sweep GC來說,有三種GC粒度可執行,分別是kGcTypeSticky、kGcTypePartial和kGcTypeFull。這三者的含義可以參考前面ART運行時垃圾收集(GC)過程分析一文。后面我們繼續對象分配過程時,也可以看到Heap類的成員變量gc_plan_的用途。
Heap類的成員函數ChangeAllocator的實現如下所示:
~~~
void Heap::ChangeAllocator(AllocatorType allocator) {
if (current_allocator_ != allocator) {
......
current_allocator_ = allocator;
MutexLock mu(nullptr, *Locks::runtime_shutdown_lock_);
SetQuickAllocEntryPointsAllocator(current_allocator_);
......
}
}
~~~
這個函數定義在文件ime/gc/heap.cc中。
Heap類的成員函數ChangeAllocator除了設置成員變量current_allocator_的值之外,還會調用函數SetQuickAllocEntryPointsAllocator來修改提供給Native Code的用來分配對象的入口點函數,以便Native Code可以在ART運行時切換GC時使用正常的接口來分配對象。這里所謂的Native Code,就是APK在安裝時通過翻譯DEX字節碼得到的本地機器指令。
了解了分配器的類型之后,接下來我們就繼續分析Heap類的成員函數AllocObjectWithAllocator的實現,如下所示:
~~~
template <bool kInstrumented, bool kCheckLargeObject, typename PreFenceVisitor>
inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, mirror::Class* klass,
size_t byte_count, AllocatorType allocator,
const PreFenceVisitor& pre_fence_visitor) {
......
if (kCheckLargeObject && UNLIKELY(ShouldAllocLargeObject(klass, byte_count))) {
return AllocLargeObject<kInstrumented, PreFenceVisitor>(self, klass, byte_count,
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<kInstrumented, false>(self, allocator, byte_count, &bytes_allocated,
&usable_size);
if (UNLIKELY(obj == nullptr)) {
bool is_current_allocator = allocator == GetCurrentAllocator();
obj = AllocateInternalWithGc(self, allocator, byte_count, &bytes_allocated, &usable_size,
&klass);
if (obj == nullptr) {
bool after_is_current_allocator = allocator == GetCurrentAllocator();
// If there is a pending exception, fail the allocation right away since the next one
// could cause OOM and abort the runtime.
if (!self->IsExceptionPending() && is_current_allocator && !after_is_current_allocator) {
// If the allocator changed, we need to restart the allocation.
return AllocObject<kInstrumented>(self, klass, byte_count, pre_fence_visitor);
}
return nullptr;
}
}
......
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);
}
......
return obj;
}
~~~
這個函數定義在文件art/runtime/gc/heap-inl.h中。
Heap類的成員函數AllocObjectWithAllocator分配對象的主要邏輯如圖2所示:

圖2 AllocObjectWithAllocator分配對象過程
首先,如果模板參數kCheckLargeObject等于true,并且要分配的是一個原子類型數組,且該為數組的大小大于預先設置的值,那么忽略掉參數allocator,而是調用Heap類的另外一個成員函數AllocLargeObject直接在Large Object Space中分配內存。后一個條件是通過調用Heap類的成員函數ShouldAllocLargeObject來判斷是否滿足的,它的實現如下所示:
~~~
inline bool Heap::ShouldAllocLargeObject(mirror::Class* c, size_t byte_count) const {
// We need to have a zygote space or else our newly allocated large object can end up in the
// Zygote resulting in it being prematurely freed.
// We can only do this for primitive objects since large objects will not be within the card table
// range. This also means that we rely on SetClass not dirtying the object's card.
return byte_count >= large_object_threshold_ && c->IsPrimitiveArray();
}
~~~
這個函數定義在文件art/runtime/gc/heap-inl.h中。
Heap類的成員變量large_object_threshold_初始化為kDefaultLargeObjectThreshold,后者又定義為3個內存頁大小。也就是說,當分配的原子類型數組大小大于等于3個內存頁時,就在Large Object Space中進行分配。
回到Heap類的成員AllocObjectWithAllocator中,如果指定了要在當前ART運行時線程的TLAB中分配對象,并且這時候當前ART運行時線程的TLAB的剩余大小大于請求分配的對象大小,那么就直接在當前線程的TLAB中分配。ART運行時線程的TLAB實際上是來自于Bump Pointer Space上的,后面我們就可以看到這一點。
如果上面的條件都不成立,接下來就調用Heap類的成員函數TryToAllocate來進行分配了。Heap類的成員函數TryToAllocate會根據參數allocator,在指定的Space分配內存,同時會根據第二個模板參數來決定是否要在允許的范圍內增加Space的大小限制,以便可以滿足分配要求。這里指定Heap類的成員函數TryToAllocate的值為false,就表示現在在不增長Space的大小限制的前提下為對象分配內存。
如果Heap類的成員函數TryToAllocate不能成功分配到指定大小的內存,那么就需要調用Heap類的成員函數AllocateInternalWithGc來先執行必要的GC,再嘗試分配請求的內存了。
如果Heap類的成員函數AllocateInternalWithGc也不能成功分配到內存,那就表明是分配失敗了。不過有一個例外,那就是ART運行時當前使用分配器類型發生了變化,這種情況就需要重新調用Heap類的成員函數AllocObject重啟分配過程。從上面的分析可以知道,當ART運行時當前使用的GC發生切換時,ART運行時當前使用的分配器類型也會隨著變化,因此這時候重新調用Heap類的成員函數AllocObject,就可以使用當前的分配器來分配對象。
假設前面成功分配了到指定的內存,接下來還有兩件事情需要做。
第一件事情是調用Heap類的成員函數AllocatorHasAllocationStack判斷參數allocator指定的分配器是否與ART運行時的Allocation Stack有關。如果有關的話,那么就需要將剛才成功分配到的對象通過調用Heap類的成員函數PushOnAllocationStack壓入到ART運行時的Allocation Stack中,以便以后可以執行Sticky GC。關于Sticky GC,可以參考前面[ART運行時垃圾收集(GC)過程分析](http://blog.csdn.net/luoshengyang/article/details/42555483)一文。
Heap類的成員函數AllocatorHasAllocationStack的實現如下所示:
~~~
class Heap {
public:
......
static ALWAYS_INLINE bool AllocatorHasAllocationStack(AllocatorType allocator_type) {
return
allocator_type != kAllocatorTypeBumpPointer &&
allocator_type != kAllocatorTypeTLAB;
}
......
};
~~~
這個函數定義在文件art/runtime/gc/heap.h中。
前面提到,ART運行時線程的TLAB是來自于Bump Pointer Space的,而Bump Pointer Space是與Compacting GC相關的,Allocation Stack是與Sticky GC相關的,這就意味著Compacting GC不會執行Sticky類型的GC。
第二件事情是調用Heap類的成員函數AllocatorMayHaveConcurrentGC判斷參數allocator指定的分配器是否與Concurrent GC相關,并且當前使用的GC就是一個Concurrent GC。如果條件都成立的話,就調用Heap類的成員函數CheckConcurrentGC檢查是否需要發起一個Concurrent GC請求。
Heap類的成員函數AllocatorMayHaveConcurrentGC的實現如下所示:
~~~
class Heap {
public:
......
static ALWAYS_INLINE bool AllocatorMayHaveConcurrentGC(AllocatorType allocator_type) {
return AllocatorHasAllocationStack(allocator_type);
}
......
};
~~~
這個函數定義在文件art/runtime/gc/heap.h中。
Heap類的成員函數AllocatorMayHaveConcurrentGC的判斷邏輯與上面分析的成員函數AllocatorHasAllocationStack是一樣的,這就意味著目前提供的Compacting GC都是非Concurrent的。不過以后是會提供具有Concurrent功能的Compacting GC的,稱為Concurrent Copying GC。
以上就是Heap類的成員函數AllocObjectWithAllocator的實現,接下來我們繼續分析Heap類的成員函數TryToAllocate和AllocateInternalWithGc的實現,以便可以更好地了解ART運行時分配對象的過程。這也有利用我們后面分析ART運行時的Compacting GC的執行過程。
Heap類的成員函數TryToAllocate的實現如下所示:
~~~
template <const bool kInstrumented, const bool kGrow>
inline mirror::Object* Heap::TryToAllocate(Thread* self, AllocatorType allocator_type,
size_t alloc_size, size_t* bytes_allocated,
size_t* usable_size) {
if (allocator_type != kAllocatorTypeTLAB &&
UNLIKELY(IsOutOfMemoryOnAllocation<kGrow>(allocator_type, alloc_size))) {
return nullptr;
}
mirror::Object* ret;
switch (allocator_type) {
case kAllocatorTypeBumpPointer: {
DCHECK(bump_pointer_space_ != nullptr);
alloc_size = RoundUp(alloc_size, space::BumpPointerSpace::kAlignment);
ret = bump_pointer_space_->AllocNonvirtual(alloc_size);
if (LIKELY(ret != nullptr)) {
*bytes_allocated = alloc_size;
*usable_size = alloc_size;
}
break;
}
case kAllocatorTypeRosAlloc: {
if (kInstrumented && UNLIKELY(running_on_valgrind_)) {
// If running on valgrind, we should be using the instrumented path.
ret = rosalloc_space_->Alloc(self, alloc_size, bytes_allocated, usable_size);
} else {
DCHECK(!running_on_valgrind_);
ret = rosalloc_space_->AllocNonvirtual(self, alloc_size, bytes_allocated, usable_size);
}
break;
}
case kAllocatorTypeDlMalloc: {
if (kInstrumented && UNLIKELY(running_on_valgrind_)) {
// If running on valgrind, we should be using the instrumented path.
ret = dlmalloc_space_->Alloc(self, alloc_size, bytes_allocated, usable_size);
} else {
DCHECK(!running_on_valgrind_);
ret = dlmalloc_space_->AllocNonvirtual(self, alloc_size, bytes_allocated, usable_size);
}
break;
}
case kAllocatorTypeNonMoving: {
ret = non_moving_space_->Alloc(self, alloc_size, bytes_allocated, usable_size);
break;
}
case kAllocatorTypeLOS: {
ret = large_object_space_->Alloc(self, alloc_size, bytes_allocated, usable_size);
// Note that the bump pointer spaces aren't necessarily next to
// the other continuous spaces like the non-moving alloc space or
// the zygote space.
DCHECK(ret == nullptr || large_object_space_->Contains(ret));
break;
}
case kAllocatorTypeTLAB: {
DCHECK_ALIGNED(alloc_size, space::BumpPointerSpace::kAlignment);
if (UNLIKELY(self->TlabSize() < alloc_size)) {
const size_t new_tlab_size = alloc_size + kDefaultTLABSize;
if (UNLIKELY(IsOutOfMemoryOnAllocation<kGrow>(allocator_type, new_tlab_size))) {
return nullptr;
}
// Try allocating a new thread local buffer, if the allocaiton fails the space must be
// full so return nullptr.
if (!bump_pointer_space_->AllocNewTlab(self, new_tlab_size)) {
return nullptr;
}
*bytes_allocated = new_tlab_size;
} else {
*bytes_allocated = 0;
}
// The allocation can't fail.
ret = self->AllocTlab(alloc_size);
DCHECK(ret != nullptr);
*usable_size = alloc_size;
break;
}
default: {
LOG(FATAL) << "Invalid allocator type";
ret = nullptr;
}
}
return ret;
}
~~~
這個函數定義在文件art/runtime/gc/heap.h中。
Heap類的成員函數TryToAllocate的實現是很直覺的,我們可以通過圖3來描述:

圖3 TryToAllocate分配對象過程
首先,如果不是指定在當前ART運行時線程的TLAB中分配對象,并且指定分配的對象大小超出了當前堆的限制,那么就會分配失敗,返回一個nullptr指針。
接下來,就根據參數allocator指定的分配器在不同的Space中分配對象:
1. 指定在Bump Pointer Space中分配對象,就調用Heap類的成員變量bump_pointer_space_指向的一個BumpPointerSpace對象的成員函數AllocNonvirtual分配指定大小的內存。
2. 指定在Ros Alloc Space中分配對象,就調用Heap類的成員變量rosalloc_space_指向的一個RosAllocSpace對象的成員函數Alloc者AllocNonvirtual分配指定大小的內存。當模板參數kInstrumented的值等于true,并且Heap類的成員變量running_on_valgrind_的值等于true時,就調用RosAllocSpace類的成員函數Alloc進行分配。否則的話,就調用RosAllocSpace類的成員函數AllocNonvirtual進行分配。從Heap類的成員變量running_on_valgrind_的命令就可以很容易推斷出,調用RosAllocSpace類的成員函數Alloc分配的內存具有非法內存訪問檢查功能,在前面[ART運行時為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/42492621)一篇文章中,我們有提到這種內存分配方式。
3. 指定在Dl Malloc Space中分配對象,就調用Heap類的成員變量dlmalloc_space_指向的一個DlMallocSpace對象的成員函數Alloc者AllocNonvirtual分配指定大小的內存。這一點與在Ros Alloc Space中分配對象的邏輯是完全一致的,除了一個是在Dl Malloc Space中分配內存, 另一個是在Ros Alloc Space中分配對象之外。
4. 指定在Non Moving Space中分配對象,就調用Heap類的成員變量non_moving_space_指向的一個RosAllocSpace對象或者DlMallocSpace對象的成員函數Alloc分配指定大小的內存。從前面[ART運行時Compacting GC堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/44789295)一文可以知道,Heap類的成員變量non_moving_space_指向的可能是一個Ros Alloc Space,也有可能是一個Dl Malloc Space。
5. 指定在Large Object Space中分配對象,就調用Heap類的成員變量large_object_space_指向的一個FreeListSpace對象或者LargeObjectMapSpace對象的成員函數Alloc分配指定大小的內存。FreeListSpace和LargeObjectMapSpace類是用來描述ART運行時的Large Object Space的,它們的實現方式在前面[ART運行時Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/42379729)一文中有介紹。
6. 指定在當前ART運行時線程的TLAB中分配對象。這種情況下首先判斷當前ART運行時線程的TLAB剩余大小是否能夠滿足分配請求的內存大小。如果不能滿足,并且沒有超出當前堆的限制,那么就會首先Heap類的成員變量bump_pointer_space_指向的一個BumpPointerSpace對象的成員函數AllocNewTlab重新分配一塊可以滿足當前請求的TLAB,然后再調用參數self描述的一個Thread對象的成員函數AllocTlab在當前ART運行時線程的TLAB中分配對象。另一方面,如果當前ART運行時線程的TLAB剩余大小不能夠滿足分配請求的內存大小,而且請求分配的內存大小又超出了當前堆的限制,那么當前分配請求就失敗了,于是就返回一個nullptr。最后,如果當前ART運行時線程的TLAB剩余大小能夠滿足分配請求的內存大小,那么就直接調用參數self描述的一個Thread對象的成員函數AllocTlab在當前ART運行時線程的TLAB中分配對象。
對于在Dl Malloc Space和Large Object Space分配對象的過程,我們在前面[ART運行時為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/42492621)一篇文章中已經分析過了,因此接下來我們就主要分析在當前ART運行時線程的TLAB、Bump Pointer Space和Ros Alloc Space中分配對象的過程,以便后面我們可以更好理解Compacting GC的執行過程。
我們首先看在當前ART運行時線程的TLAB分配對象的過程。這里又為兩個子過程。
第一個子過程是調用BumpPointerSpace類的成員函數AllocNewTlab為當前ART運行時線程分配一塊TLAB,它的實現如下所示:
~~~
bool BumpPointerSpace::AllocNewTlab(Thread* self, size_t bytes) {
MutexLock mu(Thread::Current(), block_lock_);
RevokeThreadLocalBuffersLocked(self);
byte* start = AllocBlock(bytes);
if (start == nullptr) {
return false;
}
self->SetTlab(start, start + bytes);
return true;
}
~~~
這個函數定義在文件art/runtime/gc/space/bump_pointer_space.cc中。
BumpPointerSpace類的成員函數AllocNewTlab首先是調用成員函數RevokeThreadLocalBuffersLocked撤銷當前ART運行時線程的TLAB,因為之前可能給它分配過TLAB,接著再調用成員函數AllocBlock在Bump Pointer Space中分配一塊由參數bytes指定的內存塊,并且調用Thread類的成員函數SetTlab將該內存塊設置為當前ART運行時線程新的TLAB。接下來我們就繼續分析上述三個函數的實現。
BumpPointerSpace類的成員函數RevokeThreadLocalBuffersLocked的實現如下所示:
~~~
void BumpPointerSpace::RevokeThreadLocalBuffersLocked(Thread* thread) {
objects_allocated_.FetchAndAddSequentiallyConsistent(thread->GetThreadLocalObjectsAllocated());
bytes_allocated_.FetchAndAddSequentiallyConsistent(thread->GetThreadLocalBytesAllocated());
thread->SetTlab(nullptr, nullptr);
}
~~~
這個函數定義在文件art/runtime/gc/space/bump_pointer_space.cc中。
由于ART運行時線程的TLAB只是記錄了它指向的內存塊的起始地址和結束地址,而實際的內存塊是位于Bump Pointer Space中的,因此這里就是簡單地將在當前ART運行時線程TLAB分配過的對象和內存數據匯總到Bump Pointer Space中去即可,這包括在當前ART運行時線程TLAB分配過的對象數和內存字數。最后調用Thread類的成員函數SetTlab將當前ART運行時線程的TLAB清空,就可以完成撤銷工作了。
BumpPointerSpace類的成員函數AllocBlock的實現如下所示:
~~~
byte* BumpPointerSpace::AllocBlock(size_t bytes) {
bytes = RoundUp(bytes, kAlignment);
if (!num_blocks_) {
UpdateMainBlock();
}
byte* storage = reinterpret_cast<byte*>(
AllocNonvirtualWithoutAccounting(bytes + sizeof(BlockHeader)));
if (LIKELY(storage != nullptr)) {
BlockHeader* header = reinterpret_cast<BlockHeader*>(storage);
header->size_ = bytes; // Write out the block header.
storage += sizeof(BlockHeader);
++num_blocks_;
}
return storage;
}
~~~
這個函數定義在文件art/runtime/gc/space/bump_pointer_space.cc中。
Bump Pointer Space支持按塊和按對象分配內存的方式。其中,按塊分配的內存主要就是用來作ART運行時線程的TLAB的。分配出來的內存塊有一個額外的BlockHeader,它主要是用來記錄塊的大小。
BumpPointerSpace類的成員變量num_blocks_記錄了Bump Pointer Space已經分配了多少塊內存作為當前ART運行時線程的TLAB。當它的值等于0的時候,就意味著還沒有分配過內存塊作為ART運行時線程的TLAB。這時候首先是調用BumpPointerSpace類的成員函數UpdateMainBlock記錄一下當前已經分配的對象占用的內存大小。實際上就是將最開始那塊以對角為單位分配的內存作為Bump Pointer Space的Main Block。這是一個特殊的Block,因為它沒有通過額外的BlockHeader來描述。
BumpPointerSpace類的成員函數UpdateMainBlock的實現如下所示:
~~~
void BumpPointerSpace::UpdateMainBlock() {
DCHECK_EQ(num_blocks_, 0U);
main_block_size_ = Size();
}
~~~
這個函數定義在文件art/runtime/gc/space/bump_pointer_space.cc中。
從這里我們就可以看到,BumpPointerSpace類的成員函數UpdateMainBlock主要是將Main Block的大小記錄在成員變量main_block_size_中。注意,BumpPointerSpace類的成員函數Size是從父類ContinuousSpace繼承下來的,它的職責就是返回當前已經分配出去的內存總數。
回到前面BumpPointerSpace類的成員函數AllocBlock中,接下來就會調用成員函數AllocNonvirtualWithoutAccounting執行分配內存塊的操作,它的實現如下所示:
~~~
inline mirror::Object* BumpPointerSpace::AllocNonvirtualWithoutAccounting(size_t num_bytes) {
DCHECK(IsAligned<kAlignment>(num_bytes));
byte* old_end;
byte* new_end;
do {
old_end = end_.LoadRelaxed();
new_end = old_end + num_bytes;
// If there is no more room in the region, we are out of memory.
if (UNLIKELY(new_end > growth_end_)) {
return nullptr;
}
} while (!end_.CompareExchangeWeakSequentiallyConsistent(old_end, new_end));
return reinterpret_cast<mirror::Object*>(old_end);
}
~~~
這個函數定義在文件art/runtime/gc/space/bump_pointer_space-inl.h中。
Bump Pointer Space當前已經分配出去的內存記錄在BumpPointerSpace類的成員變量end_中。只要分配大小為num_bytes的內存塊之后,不會超過當前Bump Pointer Space的限制,那么將BumpPointerSpace類的成員變量end_移動到分配的塊內存的末尾即可。這里通過一個while循環來修改BumpPointerSpace類的成員變量end_,是因為這里采用了一個非加鎖模式的多線程并發訪問資源方案。
回到BumpPointerSpace類的成員函數AllocNewTlab中,當成功分配到新的內存塊之后,接下來就可以調用Thread類的成員函數SetTlab為當前ART運行時線程設置新的TLAB了,它的實現如下所示:
~~~
void Thread::SetTlab(byte* start, byte* end) {
DCHECK_LE(start, end);
tlsPtr_.thread_local_start = start;
tlsPtr_.thread_local_pos = tlsPtr_.thread_local_start;
tlsPtr_.thread_local_end = end;
tlsPtr_.thread_local_objects = 0;
}
~~~
這個函數定義在文件/art/runtime/thread.cc中。
Thread類的成員成變量tlsPtr_指向的是一個線程局部儲存。這個線程局總儲存通過一個tls_ptr_sized_values結構體來描述。在這個tls_ptr_sized_values結構體中,成員變量thread_local_start和thread_local_end記錄了TLAB的起始地址和結束地址,另外兩個成員變量thread_local_pos和thread_local_objects分別用來記錄在當前ART運行時線程的TLAB中下一個要分配的對象的起始地址和已經在ART運行時線程的TLAB中分配出去的對象的個數。
至此,我們就分析完成了BumpPointerSpace類的成員函數AllocNewTlab為當前ART運行時線程分配一塊TLAB的子過程,接下來再看第二個子過程,即Thread類的成員函數AllocTlab的實現,如下所示:
~~~
inline mirror::Object* Thread::AllocTlab(size_t bytes) {
DCHECK_GE(TlabSize(), bytes);
++tlsPtr_.thread_local_objects;
mirror::Object* ret = reinterpret_cast<mirror::Object*>(tlsPtr_.thread_local_pos);
tlsPtr_.thread_local_pos += bytes;
return ret;
}
~~~
這個函數定義在文件/art/runtime/thread-inl.h中。
在當前ART運行時線程的TLAB中分配對象的過程很簡單,主要將用來當前ART運行時線程的線程局部儲存的一個tls_ptr_sized_values結構體的成員變量thread_local_pos向前移動參數bytes指定的大小,并且將成員變量thread_local_objects增加1即可,同時將原來成員變量thread_local_pos描述的地址值返回給調用者,作為新分配對象的起始地址。
這樣我們就分析完成了在ART運行時線程的TLAB分配對象的過程,接下來我們繼續分析BumpPointerSpace類的成員函數AllocNonvirtual的實現,以便可以了解在Bump Pointer Space分配一個普通對象的過程,它的實現如下所示:
~~~
inline mirror::Object* BumpPointerSpace::AllocNonvirtual(size_t num_bytes) {
mirror::Object* ret = AllocNonvirtualWithoutAccounting(num_bytes);
if (ret != nullptr) {
objects_allocated_.FetchAndAddSequentiallyConsistent(1);
bytes_allocated_.FetchAndAddSequentiallyConsistent(num_bytes);
}
return ret;
}
~~~
這個函數定義在文件art/runtime/gc/space/bump_pointer_space-inl.h中。
BumpPointerSpace類的成員函數AllocNonvirtual通過調用我們前面已經分析過的成員函數AllocNonvirtualWithoutAccounting來在Bump Pointer Space中分配一塊指定大小的內存,然后再增加Bump Pointer Space已經分配對象數和內存字節數即可。
從上面的分析過程就可以清楚地看到在Bump Pointer Space中分配對象的過程是非常簡單的,它只需要將下一個要分配的內存塊的地址不斷地向前推進即可。
接下來我們再看在Ros Alloc Space中分配對象的過程,即RosAllocSpace類的成員函數AllocNonvirtual的實現,如下所示:
~~~
class RosAllocSpace : public MallocSpace {
public:
......
mirror::Object* AllocNonvirtual(Thread* self, size_t num_bytes, size_t* bytes_allocated,
size_t* usable_size) {
// RosAlloc zeroes memory internally.
return AllocCommon(self, num_bytes, bytes_allocated, usable_size);
}
......
};
~~~
這個函數定義在文件art/runtime/gc/space/bump_pointer_space.h中。
RosAllocSpace類的成員函數AllocNonvirtual通過調用另外一個成員函數AllocCommon來分配指定大小的內存,后者的實現如下所示:
~~~
template<bool kThreadSafe>
inline mirror::Object* RosAllocSpace::AllocCommon(Thread* self, size_t num_bytes,
size_t* bytes_allocated, size_t* usable_size) {
size_t rosalloc_size = 0;
if (!kThreadSafe) {
Locks::mutator_lock_->AssertExclusiveHeld(self);
}
mirror::Object* result = reinterpret_cast<mirror::Object*>(
rosalloc_->Alloc<kThreadSafe>(self, num_bytes, &rosalloc_size));
if (LIKELY(result != NULL)) {
......
*bytes_allocated = rosalloc_size;
......
if (usable_size != nullptr) {
*usable_size = rosalloc_size;
}
}
return result;
}
~~~
這個函數定義在文件art/runtime/gc/space/bump_pointer_space-inl.h中。
RosAllocSpace類的成員變量rosalloc_指向的是一個RosAlloc對象。這個RosAlloc對象負責了Ros Alloc Space底層的內存管理。因此,這里就調用了RosAlloc類的成員函數Alloc來執行具體的內存分配工作。
RosAlloc類的成員函數Alloc的實現如下所示:
~~~
template<bool kThreadSafe>
inline ALWAYS_INLINE void* RosAlloc::Alloc(Thread* self, size_t size, size_t* bytes_allocated) {
if (UNLIKELY(size > kLargeSizeThreshold)) {
return AllocLargeObject(self, size, bytes_allocated);
}
void* m;
if (kThreadSafe) {
m = AllocFromRun(self, size, bytes_allocated);
} else {
m = AllocFromRunThreadUnsafe(self, size, bytes_allocated);
}
// Check if the returned memory is really all zero.
if (kCheckZeroMemory && m != nullptr) {
byte* bytes = reinterpret_cast<byte*>(m);
for (size_t i = 0; i < size; ++i) {
DCHECK_EQ(bytes[i], 0);
}
}
return m;
}
~~~
這個函數定義在文件art/runtime/gc/allocator/rosalloc-inl.h中。
如果指定分配的大小size大于常量kLargeSizeThreshold的值,那么就會調用成員函數AllocLargeObject按頁進行分配。否則的話,取決于模板參數kThreadSafe的值,也就是當前執行路徑是否是線程安全的。如果是線程安全的,就調用成員函數AllocFromRun進行分配。否則的話,就調用成員函數AllocFromRunThreadUnsafe進行分配。兩者的邏輯是基本相同的,區別就在于后者要求在獲取堆鎖的前提下進行。
常量kLargeSizeThreshold的值定義為2048,這意味著大于2KB的內存分配請求都是按頁進行分配的。接下來,我們首先分析RosAlloc類的成員函數AllocLargeObject的實現,然后再分析RosAlloc類的成員函數AllocFromRun的實現,以便可以了解RosAlloc是如何管理內存的。
RosAlloc類的成員函數AllocLargeObject的實現如下所示:
~~~
void* RosAlloc::AllocLargeObject(Thread* self, size_t size, size_t* bytes_allocated) {
......
size_t num_pages = RoundUp(size, kPageSize) / kPageSize;
void* r;
{
MutexLock mu(self, lock_);
r = AllocPages(self, num_pages, kPageMapLargeObject);
}
......
const size_t total_bytes = num_pages * kPageSize;
*bytes_allocated = total_bytes;
......
return r;
}
~~~
這個函數定義在文件art/runtime/gc/allocator/rosalloc.cc中。
RosAlloc類的成員函數AllocLargeObject首先是將請求分配的內存字節數對齊到頁大小,然后再計算得到要分配的頁數num_pages,最后調用另外一個成員函數AllocPages進行分配。
RosAlloc類的成員函數AllocPages的定義比較長,我們分段來閱讀。
第一段代碼是在一個Free Page Run列表中檢查是否有合適的FreePageRun用來分配,如下所示:
~~~
void* RosAlloc::AllocPages(Thread* self, size_t num_pages, byte page_map_type) {
lock_.AssertHeld(self);
......
FreePageRun* res = NULL;
const size_t req_byte_size = num_pages * kPageSize;
// Find the lowest address free page run that's large enough.
for (auto it = free_page_runs_.begin(); it != free_page_runs_.end(); ) {
FreePageRun* fpr = *it;
......
size_t fpr_byte_size = fpr->ByteSize(this);
......
if (req_byte_size <= fpr_byte_size) {
// Found one.
free_page_runs_.erase(it++);
......
if (req_byte_size < fpr_byte_size) {
// Split.
FreePageRun* remainder = reinterpret_cast<FreePageRun*>(reinterpret_cast<byte*>(fpr) + req_byte_size);
......
remainder->SetByteSize(this, fpr_byte_size - req_byte_size);
......
// Don't need to call madvise on remainder here.
free_page_runs_.insert(remainder);
......
fpr->SetByteSize(this, req_byte_size);
......
}
res = fpr;
break;
} else {
++it;
}
}
~~~
這個代碼片斷定義在art/runtime/gc/allocator/rosalloc.cc中。
RosAlloc類每次釋放按頁分配的內存時,都是將它們放入到成員變量free_page_runs_描述的一個空閑頁列表中,以便以后可以復用。
這段代碼的邏輯很簡單,它就是從頭開始遍歷成員變量free_page_runs_描述的空閑頁列表。如果中間找到一個Free Page Run,它的大小fpr_byte_size大于等于請求分配的大小req_byte_size,就停止遍歷。在大于的情況下,還需要將該Free Page Run的剩余大小封裝成另外一個Free Page Run,并且添加到成員變量free_page_runs_描述的空閑頁列表中去。
第二段代碼是解決第一段代碼沒有在Free Page Run列表中找到合適的Free Page Run的情況,如下所示:
~~~
// Failed to allocate pages. Grow the footprint, if possible.
if (UNLIKELY(res == NULL && capacity_ > footprint_)) {
FreePageRun* last_free_page_run = NULL;
size_t last_free_page_run_size;
auto it = free_page_runs_.rbegin();
if (it != free_page_runs_.rend() && (last_free_page_run = *it)->End(this) == base_ + footprint_) {
// There is a free page run at the end.
......
last_free_page_run_size = last_free_page_run->ByteSize(this);
} else {
// There is no free page run at the end.
last_free_page_run_size = 0;
}
......
if (capacity_ - footprint_ + last_free_page_run_size >= req_byte_size) {
// If we grow the heap, we can allocate it.
size_t increment = std::min(std::max(2 * MB, req_byte_size - last_free_page_run_size),
capacity_ - footprint_);
......
size_t new_footprint = footprint_ + increment;
size_t new_num_of_pages = new_footprint / kPageSize;
......
page_map_size_ = new_num_of_pages;
......
free_page_run_size_map_.resize(new_num_of_pages);
art_heap_rosalloc_morecore(this, increment);
if (last_free_page_run_size > 0) {
// There was a free page run at the end. Expand its size.
......
last_free_page_run->SetByteSize(this, last_free_page_run_size + increment);
......
} else {
// Otherwise, insert a new free page run at the end.
FreePageRun* new_free_page_run = reinterpret_cast<FreePageRun*>(base_ + footprint_);
......
new_free_page_run->SetByteSize(this, increment);
......
free_page_runs_.insert(new_free_page_run);
......
}
......
footprint_ = new_footprint;
// And retry the last free page run.
it = free_page_runs_.rbegin();
......
FreePageRun* fpr = *it;
......
size_t fpr_byte_size = fpr->ByteSize(this);
......
free_page_runs_.erase(fpr);
......
if (req_byte_size < fpr_byte_size) {
// Split if there's a remainder.
FreePageRun* remainder = reinterpret_cast<FreePageRun*>(reinterpret_cast<byte*>(fpr) + req_byte_size);
......
remainder->SetByteSize(this, fpr_byte_size - req_byte_size);
......
free_page_runs_.insert(remainder);
......
fpr->SetByteSize(this, req_byte_size);
......
}
res = fpr;
}
}
~~~
這個代碼片斷定義在art/runtime/gc/allocator/rosalloc.cc中。
如果本地變量res的值等于NULL,就表明前面沒有在Free Page Run列表中找到合適的Free Page Run。在這種情況下,如果當前Ros Alloc Space底層封裝的內存塊的使用大小(由成員變量footprint_描述)還沒有達到最大值(由成員變量capacity_描述),就嘗試增長內存塊的大小,以便可以滿足分配請求。
由于Ros Alloc Space底層封裝的內存塊有可能是按頁進行分配的,也有可能是按對象大小進行分配的,因此內存塊的最后一個分配單位有可能是若干個頁,也可能是一個對象。在前一種情況下,如果這若干個頁恰好就是Free Page Run列表中的最后一個Free Page Run,那么就選擇增加該Free Page Run的大小。否則的話,就選擇創建另外一個新的Free Page Run,并且添加到Free Page Run列表中去。
完成上面的操作之后,我們就可以保證Free Page Run列表的最后一個Free Page Run是一定能夠滿足分配請求的。這時候就對它執行第一段代碼類似的邏輯,即在最后一個Free Page Run的大小大于請求分配大小的情況下,對其進行分割,并且將分割出來的剩余大小封裝成另外一個Free Page Run添加Free Page Run列表中去。
第三段代碼執行收尾操作,如下所示:
~~~
if (LIKELY(res != NULL)) {
// Update the page map.
size_t page_map_idx = ToPageMapIndex(res);
......
switch (page_map_type) {
case kPageMapRun:
page_map_[page_map_idx] = kPageMapRun;
for (size_t i = 1; i < num_pages; i++) {
page_map_[page_map_idx + i] = kPageMapRunPart;
}
break;
case kPageMapLargeObject:
page_map_[page_map_idx] = kPageMapLargeObject;
for (size_t i = 1; i < num_pages; i++) {
page_map_[page_map_idx + i] = kPageMapLargeObjectPart;
}
break;
default:
LOG(FATAL) << "Unreachable - page map type: " << page_map_type;
break;
}
......
return res;
}
......
return nullptr;
}
~~~
這個代碼片斷定義在art/runtime/gc/allocator/rosalloc.cc中。
RosAlloc類有一個page_map_數組,用來記錄已經使用的每一個頁的類型,就是記錄它們是按頁分配使用的,還是按對象分配使用的,這是由參數page_map_type決定的。對于按頁使用分配出去的頁塊,第一個頁的類型記錄為kPageMapLargeObject,其余頁記錄為kPageMapLargeObjectPart。對于按對象使用分配出去的頁塊,第一個頁的類型記錄為kPageMapRun,其余頁記錄為kPageMapRunPart。
當然,設置page_map_數組是在能成功找到一個合適的Free Page Run的情況下進行的。如果沒有找到合適的Free Page Run,就直接返回一個nullptr給調用者,表示分配失敗了。
以上就是RosAlloc類按頁分配內存的過程,接下來我們繼續看按對象分配內存的過程,即RosAlloc類的成員函數AllocFromRun的實現,如下所示:
~~~
void* RosAlloc::AllocFromRun(Thread* self, size_t size, size_t* bytes_allocated) {
......
size_t bracket_size;
size_t idx = SizeToIndexAndBracketSize(size, &bracket_size);
......
void* slot_addr;
if (LIKELY(idx < kNumThreadLocalSizeBrackets)) {
// Use a thread-local run.
Run* thread_local_run = reinterpret_cast<Run*>(self->GetRosAllocRun(idx));
......
slot_addr = thread_local_run->AllocSlot();
......
if (UNLIKELY(slot_addr == nullptr)) {
// The run got full. Try to free slots.
......
MutexLock mu(self, *size_bracket_locks_[idx]);
bool is_all_free_after_merge;
// This is safe to do for the dedicated_full_run_ since the bitmaps are empty.
if (thread_local_run->MergeThreadLocalFreeBitMapToAllocBitMap(&is_all_free_after_merge)) {
......
} else {
// No slots got freed. Try to refill the thread-local run.
......
if (thread_local_run != dedicated_full_run_) {
thread_local_run->SetIsThreadLocal(false);
......
}
thread_local_run = RefillRun(self, idx);
if (UNLIKELY(thread_local_run == nullptr)) {
self->SetRosAllocRun(idx, dedicated_full_run_);
return nullptr;
}
......
thread_local_run->SetIsThreadLocal(true);
self->SetRosAllocRun(idx, thread_local_run);
......
}
......
slot_addr = thread_local_run->AllocSlot();
......
}
......
} else {
// Use the (shared) current run.
MutexLock mu(self, *size_bracket_locks_[idx]);
slot_addr = AllocFromCurrentRunUnlocked(self, idx);
......
}
......
*bytes_allocated = bracket_size;
// Caller verifies that it is all 0.
return slot_addr;
}
~~~
這個函數定義在文件art/runtime/gc/allocator/rosalloc.cc中。
RosAllocSpace對內存的管理與BumpPointerSpace對內存的管理有點類似,它們都是會將自己的一部分內存當作ART運行時線程的TLAB使用。因此,RosAlloc類的成員函數AllocFromRun就會先考慮是否要在當前ART運行時線程的局部Run進行分配。當請求分配的內存小于常量kNumThreadLocalSizeBrackets描述的值的時候,RosAlloc類的成員函數AllocFromRun就會在當前ART運行時線程的局部Run進行分配。否則的話,再在所有ART運行時線程共享的Run進行分配。
常量kNumThreadLocalSizeBrackets的值定義在11,根據我們在前面[ART運行時Compacting GC簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/44513977)一文對Runs-of-slots算法的描述,這個值對應的內存大小即為176。這就意味著小于176字節的分配請求都在當前ART運行時線程的局部Run進行分配。注意,請求分配的大小已經轉換為Run Index,即變量idx的值,因此,這里比較的是Run Index的大小,而不是直接的內存大小,不過效果是一樣的。
當決定在當前ART運行時線程的局部Run進行分配的情況下,首先要做的就是獲得當前ART運行時線程的Index值等于idx的局部Run,這可以通過參數self指向的一個Thread對象的成員函數GetRosAllocRun來獲得。
獲得了當前ART運行時線程的Index值等于idx的局部Run之后,就可以調用它的成員函數AllocSlot進行分配了。如果分配失敗,也就是該Run已經沒有空閑的Slot可用,就需要進一步處理。在前面[ART運行時Compacting GC簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/44513977)一文中,我們提到,每一個Run都有一個thread local bit map,它的作用是在釋放對象時,對應的Slot不會馬上就釋放,而是先Hold住,但是會在thread local bit map記錄它是以后需要釋放掉的。這樣當一個Run無法成功分配到Slot時,才會對那些需要釋放但是又還沒有釋放的Slot進行處理,實際上就是合并thread local bit map的信息到alloc bit map中去,這是通過調用Run類的成員函數MergeThreadLocalFreeBitMapToAllocBitMap來完成的。通過這種方式,就可以達到批量方式釋放空閑Slot的目的。
但是也有可能出現這樣的一種情況,一個Run既沒有空閑的Slot可用,而且也沒有該釋放又還沒有釋放的Slot。在這種情況下,調用Run類的成員函數MergeThreadLocalFreeBitMapToAllocBitMap就會返回false。這時候就需要給當前ART運行時線程增加一個Index值等于idx的局部Run。這個Run可以通過調用RosAlloc類的成員函數RefillRun來進行分配。
如果調用RosAlloc類的成員函數RefillRun成功分配到一個Run,那么就將該Run設置為當前ART運行時線程的局部Run,這是通過調用參數self指向的一個Thread對象的成員函數SetRosAllocRun來進行的。同時也會調用該Run的成員函數AllocSlot分配一個Slot,這時候就能夠保證是成功分配到的。
另一個方面,如果調用RosAlloc類的成員函數RefillRun不能成功分配到一個Run,這時候請求分配的內存就失敗了。在返回一個nullptr給調用者之前,RosAlloc類的成員函數AllocFromRun會做一個代碼優化,那就是將一個永遠是full狀態的并且是在所有ART運行時線程之間共享的Run設置為當前ART運行時線程的Index值為idx的局部Run。
這個永遠是full狀態的并且是在所有ART運行時線程之間共享的Run保存在RosAlloc類的成員變量dedicated_full_run_中。由于它的狀態永遠為full,因而就不能從中分配到Slot,它起到的作用就是使得我們總是可以從當前的ART運行時線程中獲得一個不為nullptr值的局部Run,這樣就可以在代碼里面省去一些空指針判斷邏輯。
以上就是在當前ART運行時線程局部Run中分配內存的過程,接下來我們繼續分析在所有ART運行時線程共享的Run中分配內存的過程,即RosAlloc類的成員函數AllocFromCurrentRunUnlocked的實現,如下所示:
~~~
inline void* RosAlloc::AllocFromCurrentRunUnlocked(Thread* self, size_t idx) {
Run* current_run = current_runs_[idx];
......
void* slot_addr = current_run->AllocSlot();
if (UNLIKELY(slot_addr == nullptr)) {
// The current run got full. Try to refill it.
......
current_run = RefillRun(self, idx);
if (UNLIKELY(current_run == nullptr)) {
// Failed to allocate a new run, make sure that it is the dedicated full run.
current_runs_[idx] = dedicated_full_run_;
return nullptr;
}
......
current_run->SetIsThreadLocal(false);
current_runs_[idx] = current_run;
......
slot_addr = current_run->AllocSlot();
......
}
return slot_addr;
}
~~~
這個函數定義在文件art/runtime/gc/allocator/rosalloc.cc中。
RosAlloc類的成員變量currents_runs_描述的就是所有的ART運行時線程都共享的Run,通過參數idx就可以獲得要在其中分配Slot的Run。獲得了這個Run之后,就可以調用它的成員函數AllocSlot進行內存分配。
在分配失敗的情況下,處理方式與前面在當前的ART運行時線程的局部Run中分配失敗Slot的邏輯類似,都是會嘗試調用RosAlloc類的成員函數RefillRun重新分配另外一個Index值為idx的Run,然后再從該Run分配Slot。如果不能重新分配到一個Index值為idx的Run,那么就會將currents_runs_數組中索引值等于idx的Run設置為dedicated_full_run_,也是為了減少代碼里的空指針判斷邏輯。
至此,在Ros Alloc Space中分配對象的過程就分要完成了,Heap類的成員函數TryToAllocate的實現也分析完成了,回到Heap類的成員函數AllocObjectWithAllocator中,我們最后需要分析的一個函數是Heap類的成員函數AllocateInternalWithGc,也就是帶GC的對象分配過程,如圖4所示:

圖4 AllocateInternalWithGc分配對象過程
接下來我們就結合Heap類的成員函數AllocateInternalWithGc的源碼來分析圖4涉及到邏輯。由于Heap類的成員函數AllocateInternalWithGc的實現也是比較長,我們分段來閱讀。
第一段代碼是檢查ART運行時當前是否正在執行GC。如果是的話,就等待當前GC完成之后,再嘗試進行對象分配,如下所示:
~~~
mirror::Object* Heap::AllocateInternalWithGc(Thread* self, AllocatorType allocator,
size_t alloc_size, size_t* bytes_allocated,
size_t* usable_size,
mirror::Class** klass) {
bool was_default_allocator = allocator == GetCurrentAllocator();
......
collector::GcType last_gc = WaitForGcToComplete(kGcCauseForAlloc, self);
if (last_gc != collector::kGcTypeNone) {
// If we were the default allocator but the allocator changed while we were suspended,
// abort the allocation.
if (was_default_allocator && allocator != GetCurrentAllocator()) {
return nullptr;
}
// A GC was in progress and we blocked, retry allocation now that memory has been freed.
mirror::Object* ptr = TryToAllocate<true, false>(self, allocator, alloc_size, bytes_allocated,
usable_size);
if (ptr != nullptr) {
return ptr;
}
}
~~~
這個代碼片段定義在文件art/runtime/gc/heap.cc中。
首先是調用Heap類的成員函數WaitForGcToComplete檢查當前是否有GC正在執行。如果有的話,就等待它執行完成。Heap類的成員函數WaitForGcToComplete的返回值last_gc不等于collector::kGcTypeNone時,就表明剛才有GC正在執行。在這種情況下,就可以調用我們前面分析過的Heap類的成員函數TryToAllocate嘗試進行一次內存分配操作了。如果分配成功,就不用再往前執行。不過如果剛才的GC執行完成之后,ART運行時當前使用的分配器發生了變化,那么就不能再繼續執行內存分配的操作了。這是由于ART運行時當前使用的分配器發生了變化,意味著參數allocator指定的分配器就不再合適使用。這種情況是有可能的,例如剛才的GC是由GC切換而發生的,這時候就會導致ART運行時當前使用的分配器發生變化。
第二段代碼嘗試執行一次GC后,再調用Heap類的成員函數TryToAllocate執行一次內存分配操作,如下所示:
~~~
collector::GcType tried_type = next_gc_type_;
const bool gc_ran =
CollectGarbageInternal(tried_type, kGcCauseForAlloc, false) != collector::kGcTypeNone;
if (was_default_allocator && allocator != GetCurrentAllocator()) {
return nullptr;
}
if (gc_ran) {
mirror::Object* ptr = TryToAllocate<true, false>(self, allocator, alloc_size, bytes_allocated,
usable_size);
if (ptr != nullptr) {
return ptr;
}
}
~~~
這個代碼片段定義在文件art/runtime/gc/heap.cc中。
這次執行的GC類型由Heap類的成員變量next_gc_type_決定。Heap類的成員變量next_gc_type_的值初始化為collector::kGcTypePartial,取值范圍為collector::kGcTypeSticky、collector::kGcTypePartial或者collector::kGcTypeFull。
如果上一次執行的GC類型不是collector::kGcTypeSticky,那么下一次執行的GC類型就為collector::kGcTypePartial或者collector::kGcTypeFull,取決于Zygote Space是否已經創建。如果已經創建,那么下一次執行的GC類型就為collector::kGcTypePartial;否則的話,就為collector::kGcTypeFull。
如果上一次執行的GC類型為collector::kGcTypeSticky,那么就取決于上一次執行的collector::kGcTypeSticky GC的垃圾回收速度,決定下一次執行的GC類型。如果上一次執行的collector::kGcTypeSticky GC的垃圾回收速度大于之前執行過的非collector::kGcTypeSticky GC的平均垃圾回收速度,并且當前分配的內存
- 前言
- 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)的過程分析