ART運行時爲新創建對象分配內存的過程分析

        ART運行時和Dalvik虛擬機一樣,在堆上爲對象分配內存時都要解決內存碎片和內存不足問題。內存碎片問題可以使用dlmalloc技術解決。內存不足問題則通過垃圾回收和在允許範圍內增長堆大小解決。由於垃圾回收會影響程序,因此ART運行時採用力度從小到大的進垃圾回收策略。一旦力度小的垃圾回收執行過後能滿足分配要求,那就不需要進行力度大的垃圾回收了。本文就詳細分析ART運行時在堆上爲對象分配內存的過程。

       從前面ART運行時Java堆創建過程分析一文可以知道,在ART運行時中,主要用來分配對象的堆空間Zygote Space和Allocation Space的底層使用的都是匿名共享內存,並且通過C庫提供的malloc和free接口來分進行管理。這樣就可以通過dlmalloc技術來儘量解決碎片問題。這一點與我們在前面Dalvik虛擬機爲新創建對象分配內存的過程分析一文提到的Dalvik虛擬機解決堆內存碎片問題的方法是一樣的。因此,接下來在分析ART運行時爲新創建對象分配的過程中,主要會分析它是如何解決內存不足的問題的。

       ART運行時爲新創建對象分配的過程如圖1所示:

圖1 ART運行時爲新創建對象分配內存的過程

        對比Dalvik虛擬機爲新創建對象分配內存的過程分析一文的圖2,可以發現,ART運行時和Dalvik虛擬機爲新創建對象分配內存的過程幾乎是一模一樣的,它們的區別僅僅是在於垃圾收集的方式和策略不同。

        從前面Android運行時ART執行類方法的過程分析一文可以知道,ART運行時爲從DEX字節碼翻譯得到的Native代碼提供的一個函數調用表中,有一個pAllocObject接口,是用來分配對象的。當ART運行時以Quick模式運行在ARM體系結構時,上述提到的pAllocObject接口由函數art_quick_alloc_object來實現。因此,接下來我們就從函數art_quick_alloc_object的實現開始分析ART運行時爲新創建對象分配內存的過程。

        函數art_quick_alloc_object的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1.     /* 
  2.      * Called by managed code to allocate an object 
  3.      */  
  4.     .extern artAllocObjectFromCode  
  5. ENTRY art_quick_alloc_object  
  6.     SETUP_REF_ONLY_CALLEE_SAVE_FRAME  @ save callee saves in case of GC  
  7.     mov    r2, r9                     @ pass Thread::Current  
  8.     mov    r3, sp                     @ pass SP  
  9.     bl     artAllocObjectFromCode     @ (uint32_t type_idx, Method* method, Thread*, SP)  
  10.     RESTORE_REF_ONLY_CALLEE_SAVE_FRAME  
  11.     RETURN_IF_RESULT_IS_NON_ZERO  
  12.     DELIVER_PENDING_EXCEPTION  
  13. 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的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. extern "C" mirror::Object* artAllocObjectFromCode(uint32_t type_idx, mirror::ArtMethod* method,  
  2.                                                   Thread* self, mirror::ArtMethod** sp)  
  3.     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {  
  4.   FinishCalleeSaveFrameSetup(self, sp, Runtime::kRefsOnly);  
  5.   return AllocObjectFromCode(type_idx, method, self, false);  
  6. }  
        這個函數定義在文件art/runtime/entrypoints/quick/quick_alloc_entrypoints.cc中。

        函數artAllocObjectFromCode又是通過調用另外一個函數AllocObjectFromCode來分配對象的。不過,在調用函數AllocObjectFromCode之前,函數artAllocObjectFromCode會先調用另外一個函數FinishCalleeSaveFrameSetup在當前調用棧幀中保存一個運行時信息。這個運行時信息描述的是接下來要調用的方法的類型爲Runtime::kRefsOnly,也就是由被調用者保存那些不是用來傳遞參數的通用寄存器,即除了r0-r3的其它通用寄存器。

        函數AllocObjectFromCode的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. // Given the context of a calling Method, use its DexCache to resolve a type to a Class. If it  
  2. // cannot be resolved, throw an error. If it can, use it to create an instance.  
  3. // When verification/compiler hasn't been able to verify access, optionally perform an access  
  4. // check.  
  5. static inline mirror::Object* AllocObjectFromCode(uint32_t type_idx, mirror::ArtMethod* method,  
  6.                                                   Thread* self,  
  7.                                                   bool access_check)  
  8.     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {  
  9.   mirror::Class* klass = method->GetDexCacheResolvedTypes()->Get(type_idx);  
  10.   Runtime* runtime = Runtime::Current();  
  11.   if (UNLIKELY(klass == NULL)) {  
  12.     klass = runtime->GetClassLinker()->ResolveType(type_idx, method);  
  13.     if (klass == NULL) {  
  14.       DCHECK(self->IsExceptionPending());  
  15.       return NULL;  // Failure  
  16.     }  
  17.   }  
  18.   if (access_check) {  
  19.     if (UNLIKELY(!klass->IsInstantiable())) {  
  20.       ThrowLocation throw_location = self->GetCurrentLocationForThrow();  
  21.       self->ThrowNewException(throw_location, "Ljava/lang/InstantiationError;",  
  22.                               PrettyDescriptor(klass).c_str());  
  23.       return NULL;  // Failure  
  24.     }  
  25.     mirror::Class* referrer = method->GetDeclaringClass();  
  26.     if (UNLIKELY(!referrer->CanAccess(klass))) {  
  27.       ThrowIllegalAccessErrorClass(referrer, klass);  
  28.       return NULL;  // Failure  
  29.     }  
  30.   }  
  31.   if (!klass->IsInitialized() &&  
  32.       !runtime->GetClassLinker()->EnsureInitialized(klass, truetrue)) {  
  33.     DCHECK(self->IsExceptionPending());  
  34.     return NULL;  // Failure  
  35.   }  
  36.   return klass->AllocObject(self);  
  37. }  
       這個函數定義在文件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執行類方法的過程分析一文,而對象類型(即類)的加載和解析過程可以參考前面Android運行時ART加載類和方法的過程分析一文。

       得到了要分配的對象的類型klass之後,如果參數access_check的值等於true,那麼就對該類型進行檢查,即檢查它是否可以實例化以及是否可以訪問。如果檢查通過,或者不需要檢查,那麼接下來還要確保類型klass是已經初始化過了的。前面的檢查都沒有問題之後,最後函數AllocObjectFromCode就調用Class類的成員函數AllocObject來分配一個類型爲klass的對象。

       Class類的成員函數AllocObject的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. Object* Class::AllocObject(Thread* self) {  
  2.   ......  
  3.   return Runtime::Current()->GetHeap()->AllocObject(self, thisthis->object_size_);  
  4. }  
      這個函數定義在文件art/runtime/mirror/class.cc中。

      這裏我們就終於看到調用ART運行時內部的Heap對象的成員函數AllocObject在堆上分配對象了,其中,要分配的大小保存在當前Class對象的成員變量object_size_中。

      Heap類的成員函數AllocObject的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. mirror::Object* Heap::AllocObject(Thread* self, mirror::Class* c, size_t byte_count) {  
  2.   ......  
  3.   
  4.   mirror::Object* obj = NULL;  
  5.   size_t bytes_allocated = 0;  
  6.   ......  
  7.   
  8.   bool large_object_allocation =  
  9.       byte_count >= large_object_threshold_ && have_zygote_space_ && c->IsPrimitiveArray();  
  10.   if (UNLIKELY(large_object_allocation)) {  
  11.     obj = Allocate(self, large_object_space_, byte_count, &bytes_allocated);  
  12.     ......  
  13.   } else {  
  14.     obj = Allocate(self, alloc_space_, byte_count, &bytes_allocated);  
  15.     ......  
  16.   }  
  17.   
  18.   if (LIKELY(obj != NULL)) {  
  19.     obj->SetClass(c);  
  20.     ......  
  21.   
  22.     RecordAllocation(bytes_allocated, obj);  
  23.     ......  
  24.   
  25.     if (UNLIKELY(static_cast<size_t>(num_bytes_allocated_) >= concurrent_start_bytes_)) {  
  26.       ......  
  27.       SirtRef<mirror::Object> ref(self, obj);  
  28.       RequestConcurrentGC(self);  
  29.     }  
  30.     ......  
  31.   
  32.     return obj;  
  33.   } else {  
  34.     ......  
  35.     self->ThrowOutOfMemoryError(oss.str().c_str());  
  36.     return NULL;  
  37.   }  
  38. }  

        這個函數定義在文件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堆創建過程分析一文可以知道,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的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. inline void Heap::RecordAllocation(size_t size, mirror::Object* obj) {  
  2.   DCHECK(obj != NULL);  
  3.   DCHECK_GT(size, 0u);  
  4.   num_bytes_allocated_.fetch_add(size);  
  5.   
  6.   if (Runtime::Current()->HasStatsEnabled()) {  
  7.     RuntimeStats* thread_stats = Thread::Current()->GetStats();  
  8.     ++thread_stats->allocated_objects;  
  9.     thread_stats->allocated_bytes += size;  
  10.   
  11.     // TODO: Update these atomically.  
  12.     RuntimeStats* global_stats = Runtime::Current()->GetStats();  
  13.     ++global_stats->allocated_objects;  
  14.     global_stats->allocated_bytes += size;  
  15.   }  
  16.   
  17.   // This is safe to do since the GC will never free objects which are neither in the allocation  
  18.   // stack or the live bitmap.  
  19.   while (!allocation_stack_->AtomicPushBack(obj)) {  
  20.     CollectGarbageInternal(collector::kGcTypeSticky, kGcCauseForAlloc, false);  
  21.   }  
  22. }  
        這個函數定義在文件art/runtime/gc/heap.cc中。

        Heap類的成員函數RecordAllocation首先是記錄當前已經分配的內存字節數以及對象數,接着再將新分配的對象壓入到Heap類的成員變量allocation_stack_描述的Allocation Stack中去。後面這一點與Dalvik虛擬機的做法是不一樣的。Dalvik虛擬機直接將新分配出來的對象記錄在Live Bitmap中,具體可以參考前面Dalvik虛擬機爲新創建對象分配內存的過程分析一文。ART運行時之所以要將新分配的對象壓入到Allocation Stack中去,是爲了以後可以執行Sticky GC。

        注意,如果不能成功將新分配的對角壓入到Allocation Stack中,就說明上次GC以來,新分配的對象太多了,因此這時候就需要執行一個Sticky GC,將Allocation Stack裏面的垃圾進行回收,然後再嘗試將新分配的對象壓入到Allocation Stack中,直到成功爲止。

        接下來我們就重點分析Heap類的成員函數Allocate的實現,以便可以瞭解新創建對象在堆上分配的具體過程,如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. template <class T>  
  2. inline mirror::Object* Heap::Allocate(Thread* self, T* space, size_t alloc_size,  
  3.                                       size_t* bytes_allocated) {  
  4.   ......  
  5.   
  6.   mirror::Object* ptr = TryToAllocate(self, space, alloc_size, false, bytes_allocated);  
  7.   if (ptr != NULL) {  
  8.     return ptr;  
  9.   }  
  10.   return AllocateInternalWithGc(self, space, alloc_size, bytes_allocated);  
  11. }  

        這個函數定義在文件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,它們的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. inline mirror::Object* Heap::TryToAllocate(Thread* self, space::AllocSpace* space, size_t alloc_size,  
  2.                                            bool grow, size_t* bytes_allocated) {  
  3.   if (UNLIKELY(IsOutOfMemoryOnAllocation(alloc_size, grow))) {  
  4.     return NULL;  
  5.   }  
  6.   return space->Alloc(self, alloc_size, bytes_allocated);  
  7. }  
  8.   
  9. // DlMallocSpace-specific version.  
  10. inline mirror::Object* Heap::TryToAllocate(Thread* self, space::DlMallocSpace* space, size_t alloc_size,  
  11.                                            bool grow, size_t* bytes_allocated) {  
  12.   if (UNLIKELY(IsOutOfMemoryOnAllocation(alloc_size, grow))) {  
  13.     return NULL;  
  14.   }  
  15.   if (LIKELY(!running_on_valgrind_)) {  
  16.     return space->AllocNonvirtual(self, alloc_size, bytes_allocated);  
  17.   } else {  
  18.     return space->Alloc(self, alloc_size, bytes_allocated);  
  19.   }  
  20. }  
        這兩個函數定義在文件art/runtime/gc/heap.cc中。

        Heap類兩個重載版本的成員函數TryToAllocate的實現邏輯都幾乎是相同的,首先是調用另外一個成員函數IsOutOfMemoryOnAllocation判斷分配請求的內存後是否會超過堆的大小限制。如果超過,則分配失敗;否則的話再在指定的Space進行內存分配。

        Heap類的成員函數IsOutOfMemoryOnAllocation的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. inline bool Heap::IsOutOfMemoryOnAllocation(size_t alloc_size, bool grow) {  
  2.   size_t new_footprint = num_bytes_allocated_ + alloc_size;  
  3.   if (UNLIKELY(new_footprint > max_allowed_footprint_)) {  
  4.     if (UNLIKELY(new_footprint > growth_limit_)) {  
  5.       return true;  
  6.     }  
  7.     if (!concurrent_gc_) {  
  8.       if (!grow) {  
  9.         return true;  
  10.       } else {  
  11.         max_allowed_footprint_ = new_footprint;  
  12.       }  
  13.     }  
  14.   }  
  15.   return false;  
  16. }  
        這個函數定義在文件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堆創建過程分析一文可以知道,對於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的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated) {  
  2.   MemMap* mem_map = MemMap::MapAnonymous("large object space allocation", NULL, num_bytes,  
  3.                                          PROT_READ | PROT_WRITE);  
  4.   if (mem_map == NULL) {  
  5.     return NULL;  
  6.   }  
  7.   MutexLock mu(self, lock_);  
  8.   mirror::Object* obj = reinterpret_cast<mirror::Object*>(mem_map->Begin());  
  9.   large_objects_.push_back(obj);  
  10.   mem_maps_.Put(obj, mem_map);  
  11.   size_t allocation_size = mem_map->Size();  
  12.   DCHECK(bytes_allocated != NULL);  
  13.   *bytes_allocated = allocation_size;  
  14.   num_bytes_allocated_ += allocation_size;  
  15.   total_bytes_allocated_ += allocation_size;  
  16.   ++num_objects_allocated_;  
  17.   ++total_objects_allocated_;  
  18.   return obj;  
  19. }  
       這個函數定義在文件art/runtime/gc/space/large_object_space.cc中。

       從這裏就可以看到,Large Object Map Space分配內存的邏輯是很簡單的,直接就是調用MemMap類的靜態成員函數MapAnonymous創建一塊指定大小的匿名內存,然後再將該匿名共享內存添加到成員變量large_objects_描述的一個向量中去,最後更新內部的各個統計數據。

       DlMallocSpace類的成員函數Alloc的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. mirror::Object* DlMallocSpace::Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated) {  
  2.   return AllocNonvirtual(self, num_bytes, bytes_allocated);  
  3. }  
       這個函數定義在文件art/runtime/gc/space/dlmalloc_space.cc中。

       DlMallocSpace類的成員函數Alloc調用另外一個成員函數AllocNonvirtual來進行內存分配,後者的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. inline mirror::Object* DlMallocSpace::AllocNonvirtual(Thread* self, size_t num_bytes,  
  2.                                                       size_t* bytes_allocated) {  
  3.   mirror::Object* obj;  
  4.   {  
  5.     MutexLock mu(self, lock_);  
  6.     obj = AllocWithoutGrowthLocked(num_bytes, bytes_allocated);  
  7.   }  
  8.   if (obj != NULL) {  
  9.     // Zero freshly allocated memory, done while not holding the space's lock.  
  10.     memset(obj, 0, num_bytes);  
  11.   }  
  12.   return obj;  
  13. }  
       這個函數定義在文件art/runtime/gc/space/dlmalloc_space-inl.h中。

       DlMallocSpace類的成員函數AllocNonvirtual首先是調用另外一個成員函數AllocWithoutGrowthLocked在不增長Space的大小的前提下進行內存分配,分配成功之後再調用函數memset對分配出來的內存進行清空,最後將分配出來的內存返回給調用者。

       DlMallocSpace類的成員函數AllocWithoutGrowthLocked的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. inline mirror::Object* DlMallocSpace::AllocWithoutGrowthLocked(size_t num_bytes, size_t* bytes_allocated) {  
  2.   mirror::Object* result = reinterpret_cast<mirror::Object*>(mspace_malloc(mspace_, num_bytes));  
  3.   if (result != NULL) {  
  4.     if (kDebugSpaces) {  
  5.       CHECK(Contains(result)) << "Allocation (" << reinterpret_cast<void*>(result)  
  6.             << ") not in bounds of allocation space " << *this;  
  7.     }  
  8.     size_t allocation_size = AllocationSizeNonvirtual(result);  
  9.     DCHECK(bytes_allocated != NULL);  
  10.     *bytes_allocated = allocation_size;  
  11.     num_bytes_allocated_ += allocation_size;  
  12.     total_bytes_allocated_ += allocation_size;  
  13.     ++total_objects_allocated_;  
  14.     ++num_objects_allocated_;  
  15.   }  
  16.   return result;  
  17. }  
        這個函數定義在文件art/runtime/gc/space/dlmalloc_space-inl.h中。

        從前面ART運行時Java堆創建過程分析一文可以知道,DlMallocSpace底層使用的匿名共享內存塊被封裝成一個mspace對象,並且保存在成員變量mspace_中,因此這裏就可以直接調用C庫提供的mspace_malloc接口進行內存分配。使用mspace_malloc分配的內存會自動被清空,因此這裏不用再手動清空。DlMallocSpace類的成員函數AllocWithoutGrowthLocked在將分配出來的內存返回給調用者之前,同樣是會更新內部的各個統計數據。

        回到前面Heap類的成員函數Allocate中,在調用成員函數TryToAllocate不能成功分配指定大小的內存塊之後,接下來就繼續調用成員函數AllocateInternalWithGc執行帶GC的內存分配,它的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. mirror::Object* Heap::AllocateInternalWithGc(Thread* self, space::AllocSpace* space,  
  2.                                              size_t alloc_size, size_t* bytes_allocated) {  
  3.   mirror::Object* ptr;  
  4.   
  5.   // The allocation failed. If the GC is running, block until it completes, and then retry the  
  6.   // allocation.  
  7.   collector::GcType last_gc = WaitForConcurrentGcToComplete(self);  
  8.   if (last_gc != collector::kGcTypeNone) {  
  9.     // A GC was in progress and we blocked, retry allocation now that memory has been freed.  
  10.     ptr = TryToAllocate(self, space, alloc_size, false, bytes_allocated);  
  11.     if (ptr != NULL) {  
  12.       return ptr;  
  13.     }  
  14.   }  
  15.   
  16.   // Loop through our different Gc types and try to Gc until we get enough free memory.  
  17.   for (size_t i = static_cast<size_t>(last_gc) + 1;  
  18.       i < static_cast<size_t>(collector::kGcTypeMax); ++i) {  
  19.     bool run_gc = false;  
  20.     collector::GcType gc_type = static_cast<collector::GcType>(i);  
  21.     switch (gc_type) {  
  22.       case collector::kGcTypeSticky: {  
  23.           const size_t alloc_space_size = alloc_space_->Size();  
  24.           run_gc = alloc_space_size > min_alloc_space_size_for_sticky_gc_ &&  
  25.               alloc_space_->Capacity() - alloc_space_size >= min_remaining_space_for_sticky_gc_;  
  26.           break;  
  27.         }  
  28.       case collector::kGcTypePartial:  
  29.         run_gc = have_zygote_space_;  
  30.         break;  
  31.       case collector::kGcTypeFull:  
  32.         run_gc = true;  
  33.         break;  
  34.       default:  
  35.         break;  
  36.     }  
  37.   
  38.     if (run_gc) {  
  39.       // If we actually ran a different type of Gc than requested, we can skip the index forwards.  
  40.       collector::GcType gc_type_ran = CollectGarbageInternal(gc_type, kGcCauseForAlloc, false);  
  41.       DCHECK_GE(static_cast<size_t>(gc_type_ran), i);  
  42.       i = static_cast<size_t>(gc_type_ran);  
  43.   
  44.       // Did we free sufficient memory for the allocation to succeed?  
  45.       ptr = TryToAllocate(self, space, alloc_size, false, bytes_allocated);  
  46.       if (ptr != NULL) {  
  47.         return ptr;  
  48.       }  
  49.     }  
  50.   }  
  51.   
  52.   // Allocations have failed after GCs;  this is an exceptional state.  
  53.   // Try harder, growing the heap if necessary.  
  54.   ptr = TryToAllocate(self, space, alloc_size, true, bytes_allocated);  
  55.   if (ptr != NULL) {  
  56.     return ptr;  
  57.   }  
  58.   
  59.   ......  
  60.   
  61.   // We don't need a WaitForConcurrentGcToComplete here either.  
  62.   CollectGarbageInternal(collector::kGcTypeFull, kGcCauseForAlloc, true);  
  63.   return TryToAllocate(self, space, alloc_size, true, bytes_allocated);  
  64. }  
        這個函數定義在文件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堆創建過程分析一文可以知道,當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運行時面臨的最大挑戰就是內存不足問題,它要通過在允許範圍內增長堆大小以及垃圾回收兩個手段來解決。其中,垃圾回收會對程序造成影響,因此在執行垃圾回收時,使用的力度要從小到大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章