虛幻4垃圾回收剖析

上一個系列的文章咱們已經對虛幻4中的反射實現原理進行了一個簡單得講解,反射的用途很是多,其中一個就是用來作垃圾回收用的,咱們這個系列就對虛幻4中的垃圾回收機制作一個講解。注:本系列文章對應的虛幻4版本是4.14.1html

垃圾回收 python

在計算機科學中,垃圾回收(garbage collection, 縮寫GC)是一種自動的內存管理機制。當一個電腦上的動態內存不須要時,就應該予以釋放,這種自動內存的資源管理,稱爲垃圾回收。垃圾回收能夠減小程序員的負擔,也能減小程序員犯錯的機會。最先起源於LISP語言。目前許多語言如Smalltalk、Java、C#、python和D語言等都支持垃圾回收。程序員

下面咱們簡單的介紹下垃圾回收常見的分類以及實現算法,咱們並不會特別細緻的去講,若是讀者有興趣能夠自行查找相關的書籍和文獻。推薦你們看下參考文獻中2的文章。算法

算法分類 數組

引用計數GC和追蹤式GC 多線程

引用計數式GC經過額外的計數來實時計算對單個對象的引用次數,當引用次數爲0時回收對象。引用計數的GC是實時的。像微軟COM對象的加減引用值以及C++中的智能指針都是經過引用計數來實現GC的。框架

 

追蹤式GC算法在達到GC條件時(強制GC或者內存不夠用、到達GC間隔時間)經過掃描系統中是否有對象的引用來判斷對象是否存活,而後回收無用對象。less

保守式GC和精確式GC 編輯器

精確式GC是指在回收過程當中能準確得識別和回收每個無用對象的GC方式,爲了準確識別每個對象的引用,經過須要一些額外的數據(好比虛幻中的屬性UProperty)。ide

 

保守式GC並不能準備識別每個無用的對象(好比在32位程序中的一個4字節的值,它是不能判斷出它是一個對象指針或者是一個數字的),可是能保證在不會錯誤的回收存活的對象的狀況下回收一部分無用對象。保守式GC不須要額外的數據來支持查找對象的引用,它將全部的內存數據假定爲指針,經過一些條件來斷定這個指針是不是一個合法的對象。

搬遷式和非搬遷式

搬遷式GC在GC過程當中須要移動對象在內存中的位置,固然移動對象位置後須要將全部引用到這個對象的地方更新到新位置(有的經過句柄來實現、而有的可能須要修改全部引用內存的指針)。

 

非搬遷式GC跟搬遷式GC正好相關,在GC過程當中不須要移動對象的內存位置。

實時和非實現GC

實時GC是指不須要中止用戶執行的GC方式。而非實時GC則須要中止用戶程序的執行(stop the world)。

漸進式和非漸進式GC

和實時GC同樣不須要中斷用戶程序運行,不一樣的地方在於漸進式GC不會在對象拋棄時當即回收佔用的內存資源,而在GC達成必定條件時進行回收操做。

回收算法

引用計數式

引用計數算法是即時的,漸近的,對於交互式和實時系統比較友好,由於它幾乎不會對用戶程序形成明顯的停頓

 

優勢:

  • 引用計數方法是漸進式的,它能及時釋放無用的對象,將內存管理的的開銷實時的分佈在用戶程序運行過程當中。

缺點:

  • 引用計數方法要求修改一個對象引用時必須調整舊對象的引用計數和新對象的引用計數,這些操做增長了指針複製的成本,在整體開銷上而言一般比追蹤式GC要大。
  • 引用計數要求額外的空間來保存計數值,這一般要求框架和編譯器支持。
  • 實際應用中不少對象的生命週期很短,頻繁的分配和釋放致使內存碎片化嚴重。內存碎片意味着可用內存在總數量上足夠但因爲不連續於是實際上不可用,同時增長內存分配的時間。
  • 引用計數算法最嚴重的問題是環形引用問題(固然能夠經過弱指針來解決)。

追蹤式GC

追蹤式GC算法經過遞歸的檢查對象的可達性來判斷對象是否存活,進而回收無用內存。

追蹤式的GC算法的關鍵在於準確並快速的找到全部可達對象,不可達的對象對於用戶程序來講是不可見的,所以清掃階段一般能夠和用戶程序並行執行。下面主要討論了算法的標記階段的實現。

標記清掃(Mark-Sweep)

標記清掃式GC算法是後面介紹的追蹤式GC算法的基礎,它經過搜索整個系統中對對象的引用來檢查對象的可達性,以肯定對象是否須要回收。

分類:追蹤式,非實時,保守(非搬遷式)或者精確式(搬遷式) ,非漸進

 

優勢:

  • 相對於引用計數算法,徹底沒必要考慮環形引用問題。
  • 操縱指針時沒有額外的開銷。
  • 與用戶程序徹底分離。

缺點:

  • 標記清掃算法是非實時的,它要求在垃圾收集器運行時暫停用戶程序運行,這對於實時和交互式系統的影響很是大。
  • 基本的標記清掃算法一般在回收內存時會同時合併相鄰空閒內存塊,然而在系統運行一段時間後仍然不免會生成大量內存碎片,內存碎片意味着可用內存的總數量上足夠但實際上不可用,同時還會增長分配內存的時間,下降內存訪問的效率。
  • 保守式的標記清掃算法可能會將某些無用對象當作存活對象,致使內存泄露。

 

用戶程序初始化時向系統預申請一塊內存,新的對象申請在此區域內分配, 用戶程序不須要主動釋放己分配的空間,當達到回收條件,或者用戶程序主動請求時開始收集內存。

標記清掃式GC算法(mark-sweep)分爲兩個階段: 標記階段 和 清掃階段。

標記階段

從根結點集合開始遞歸的標記全部可達對象。

 

根結點集合一般包括全部的全局變量,靜態變量以及棧區(注2)。這些數據能夠被用戶程序直接或者間接引用到。

標記前:

標記後:

清掃階段

遍歷全部對象,將沒有標記爲可達的對象回收,並清理標記位。

 

保守式的標記清掃算法:

 

保守式的標記清掃算法缺乏對象引用的內存信息(事實上它自己就爲了這些Uncooperative Environment設計的),它假定全部根結點集合爲指針,遞歸的將這些指針指向的內存堆區標記爲可達,並將全部可達區域的內存數據假定爲指針,重複上一步,最終識別出不可達的內存區域,並將這些區域回收。

 

保守式的GC算法可能致使內存泄漏。因爲保守式GC算法沒有必需的GC信息,所以必須假設全部內存數據是一個指針,這極可能將一個非指針數據看成指針,好比將一個整型值看成一個指針,而且這個值碰巧在已經分配的堆區域地址範圍內,這將會致使這部份內存被標記爲可達,進而不能被回收。

 

保守式的GC不能肯定一個內存上數據是不是一個指針,所以不能移動對象的位置。

 

實際應用:

保守式標記清掃GC算法: Boehm-Demers-Weiser 算法

精確式標記清掃算法:UE3, UE4等

 

因爲咱們並非主要介紹GC算法的,因此接下來咱們不打算對其它的GC算法進行細講,讀者能夠參考參考文獻中第2篇文章或者看垃圾回收的算法與實現這本書,內容比較全面。

標記縮並

有些狀況下內存管理的性能瓶頸在分配階段,內存碎片增長了查找可用內存區域的開銷,標記縮並算法就是爲了處理內存碎片問題而產生的。

 

分類:追蹤式,非實時,精確式,搬遷式,非漸進

節點複製

節點複製GC經過將全部存活對象從一個區移動到另外一個區來過濾非存活對象。

分類:追蹤式,非實時,精確式,搬遷式,非漸進

分代式GC(Generational Garbage Collection)

在程序運行過程當中,許多對象的生命週期是短暫的,分配不久即被拋棄。所以將內存回收的工做焦點集中在這些最有多是垃圾的對象上,能夠提升內存回收的效率,下降回收過程的開銷,進而減小對用戶程序的中斷。

 

分代式GC每次回收時經過掃描整個堆中的一部分而是否是所有來下降內存回收過程的開銷。

 

分類:追蹤式,非實時,精確式,搬遷式,非漸進

實際應用:Java, Ruby

漸進式GC

漸進式GC要解決的問題是如何縮短自動內存管理引發的中斷,以下降對實時系統的影響。

 

漸進式GC算法基於分代式GC算法,它的核心在於在用戶程序運行過程當中維護年輕分代的根結點集合。

 

分類:追蹤式,非實時,精確式,搬遷式,漸進式

實際應用:Java, Ruby

虛幻4 中的GC

經過上面咱們簡單的對GC分類和算法的講解,再結合虛幻4 的代碼,咱們能夠肯定它的GC是追蹤式、非實時、精確式,非漸近、增量回收(時間片)。下面咱們就從它的UML圖、執行流程以及部分代碼講起。

虛幻4中GC的入口是CollectGarbage(),讓咱們來看一下它的函數原型以及定義

 1 /**
 2 
 3 * Deletes all unreferenced objects, keeping objects that have any of the passed in KeepFlags set. Will wait for other threads to unlock GC.
 4 
 5 *
 6 
 7 * @param    KeepFlags            objects with those flags will be kept regardless of being referenced or not
 8 
 9 * @param    bPerformFullPurge    if true, perform a full purge after the mark pass
10 
11 */
12 
13 COREUOBJECT_API void CollectGarbage(EObjectFlags KeepFlags, bool bPerformFullPurge = true);
14 
15 void CollectGarbage(EObjectFlags KeepFlags, bool bPerformFullPurge)
16 
17 {
18 
19     // No other thread may be performing UOBject operations while we're running
20 
21     GGarbageCollectionGuardCritical.GCLock();
22 
23  
24 
25     // Perform actual garbage collection
26 
27     CollectGarbageInternal(KeepFlags, bPerformFullPurge);
28 
29  
30 
31     // Other threads are free to use UObjects
32 
33     GGarbageCollectionGuardCritical.GCUnlock();
34 
35 }

 

從這段代碼中咱們能夠獲得以下信息,它是增量式的(bPerformFullPurge),非實時的(gc 鎖),最後調用了CollectGarbageInternal來執行真正的垃圾回收操做。

CollectGarbageInternal流程

經過看下面的流程圖,咱們即可以知道虛幻4垃圾回收就是像咱們上面所說的那樣,分爲標記刪除階段,只不過它多了一個簇(cluster)和增量回收的,而增量回收是爲了不垃圾回收時致使的卡頓的,提出簇的概念是爲了提升回收的效率的。

標記階段(Mark)

虛幻4 中垃圾標記階段是經過FRealtimeGC類中的PerformReachabilityAnalysis()函數來完成標記的。

 1     /**
 2 
 3      * Performs reachability analysis.
 4 
 5      *
 6 
 7      * @param KeepFlags        Objects with these flags will be kept regardless of being referenced or not
 8 
 9      */
10 
11     void PerformReachabilityAnalysis(EObjectFlags KeepFlags, bool bForceSingleThreaded = false)
12 
13     {        
14 
15         /** Growing array of objects that require serialization */
16 
17         TArray<UObject*>& ObjectsToSerialize = *FGCArrayPool::Get().GetArrayFromPool();
18 
19  
20 
21         // Reset object count.
22 
23         GObjectCountDuringLastMarkPhase = 0;
24 
25  
26 
27         // Presize array and add a bit of extra slack for prefetching.
28 
29         ObjectsToSerialize.Reset( GUObjectArray.GetObjectArrayNumMinusPermanent() + 3 );
30 
31         // Make sure GC referencer object is checked for references to other objects even if it resides in permanent object pool
32 
33         if (FPlatformProperties::RequiresCookedData() && FGCObject::GGCObjectReferencer && GUObjectArray.IsDisregardForGC(FGCObject::GGCObjectReferencer))
34 
35         {
36 
37             ObjectsToSerialize.Add(FGCObject::GGCObjectReferencer);
38 
39         }
40 
41  
42 
43         MarkObjectsAsUnreachable(ObjectsToSerialize, KeepFlags);
44 
45  
46 
47         {
48 
49             FGCReferenceProcessor ReferenceProcessor;
50 
51             TFastReferenceCollector<FGCReferenceProcessor, FGCCollector, FGCArrayPool> ReferenceCollector(ReferenceProcessor, FGCArrayPool::Get());
52 
53             ReferenceCollector.CollectReferences(ObjectsToSerialize, bForceSingleThreaded);
54 
55         }
56 
57         FGCArrayPool::Get().ReturnToPool(&ObjectsToSerialize);
58 
59     }

 

咱們能夠看到,它首先會調用MarkObjectsAsUnreachable()來把全部不帶KeepFlags標記和EinternalObjectFlags::GarbageCollectionKeepFlags標記的對象所有標記爲不可達,並把它們添加到ObjectsToSerialize中去。這個函數會判斷當前的FUObjectItem::GetOwnerIdnex()是否爲0,若是爲0那麼它就是一個普通物體也就意味着不在簇(cluster)中。把全部符合條件的對象標記爲不可達後,而後會調用下面的代碼來進行可達性標記。

1        {
2 
3             FGCReferenceProcessor ReferenceProcessor;
4 
5             TFastReferenceCollector<FGCReferenceProcessor, FGCCollector, FGCArrayPool> ReferenceCollector(ReferenceProcessor, FGCArrayPool::Get());
6 
7             ReferenceCollector.CollectReferences(ObjectsToSerialize, bForceSingleThreaded);
8 
9         }

 

下面咱們來詳細講解標記的過程,這裏會牽扯到咱們前面提到的UProperty和UClass也就是咱們須要利用反射信息來進行可達性的標記。看上面的調用,有幾個比較重要的對象TFastReferenceCollector、FGCReferenceProcessor、以及FGCCollector,下面咱們來分別介紹下下面幾個類。

TFastReferenceCollector

CollectReferences用於可達性分析,若是是單線程的話就調用ProcessObjectArray()遍歷UObject的記號流(token stream )來查找存在的引用,不然會建立幾個FCollectorTask來處理,最終調用的仍是ProcessObjectArray()函數來處理。下面咱們來仔細來說解一下這個函數。

 

它會遍歷InObjectsToSerializeArray中的UObject對象,而後根據這個類的UClass拿到它的FGCReferenceTokenStream,若是是單線程且bAutoGenerateTokenSteram爲true,且沒有產生token stream,那麼會調用AssembleReferenceTokenStream()來生成,代碼以下所示:

 1 // Make sure that token stream has been assembled at this point as the below code relies on it.
 2 
 3 if (bAutoGenerateTokenStream && !ReferenceProcessor.IsRunningMultithreaded())
 4 
 5 {
 6 
 7     UClass* ObjectClass = CurrentObject->GetClass();
 8 
 9     if (!ObjectClass->HasAnyClassFlags(CLASS_TokenStreamAssembled))
10 
11     {
12 
13         ObjectClass->AssembleReferenceTokenStream();
14 
15     }
16 
17 }

 

而後它會根據當前的ReferenceTokenSteramIndex來獲取FGCReferenceInfo,而後根據它的類型來作相應的操做,代碼以下所示:

 1 TokenStreamIndex++;
 2 
 3 FGCReferenceInfo ReferenceInfo = TokenStream->AccessReferenceInfo(ReferenceTokenStreamIndex);
 4 
 5  
 6 
 7 if (ReferenceInfo.Type == GCRT_Object)
 8 
 9 {
10 
11     // We're dealing with an object reference.
12 
13     UObject**    ObjectPtr = (UObject**)(StackEntryData + ReferenceInfo.Offset);
14 
15     UObject*&    Object = *ObjectPtr;
16 
17     TokenReturnCount = ReferenceInfo.ReturnCount;
18 
19     ReferenceProcessor.HandleTokenStreamObjectReference(NewObjectsToSerialize, CurrentObject, Object, ReferenceTokenStreamIndex, true);
20 
21 }
22 
23 else if (ReferenceInfo.Type == GCRT_ArrayObject)
24 
25 {
26 
27     // We're dealing with an array of object references.
28 
29     TArray<UObject*>& ObjectArray = *((TArray<UObject*>*)(StackEntryData + ReferenceInfo.Offset));
30 
31     TokenReturnCount = ReferenceInfo.ReturnCount;
32 
33     for (int32 ObjectIndex = 0, ObjectNum = ObjectArray.Num(); ObjectIndex < ObjectNum; ++ObjectIndex)
34 
35     {
36 
37         ReferenceProcessor.HandleTokenStreamObjectReference(NewObjectsToSerialize, CurrentObject, ObjectArray[ObjectIndex], ReferenceTokenStreamIndex, true);
38 
39     }
40 
41 }
42 
43 ...

 

在這個處理的過程當中,若是新加入的對象數據大於必定數量(MinDesiredObjectsPerSubTask)且是多線程處理,那麼就會按需建立一些新的TGraphTask<FCollectorTask>來並行處理引用問題,那麼這就是一個遞歸的過程了。具體的代碼讀者能夠自行去閱讀,這裏就不展開去講了。

 

還記得咱們前面一塊兒提到的就是,反射信息用於GC嗎?UClass::AssembleReferenceTokenStream()函數就是用生成記號流(token steam,其實就是記錄了什麼地方有UObject引用),它有一個CLASS_TokenStreamAssembled來保存只須要初始化一次。

這裏咱們只留一部分的代碼,讀者能夠自行查看AssembleReferenceTokenStream()的定義:

 1 void UClass::AssembleReferenceTokenStream(bool bForce)
 2 
 3 {
 4 
 5     if (!HasAnyClassFlags(CLASS_TokenStreamAssembled) || bForce)
 6 
 7     {
 8 
 9         if (bForce)
10 
11         {
12 
13             ReferenceTokenStream.Empty();
14 
15             ClassFlags &= ~CLASS_TokenStreamAssembled;
16 
17         }
18 
19         TArray<const UStructProperty*> EncounteredStructProps;
20 
21         // Iterate over properties defined in this class
22 
23         for( TFieldIterator<UProperty> It(this,EFieldIteratorFlags::ExcludeSuper); It; ++It)
24 
25         {
26 
27             UProperty* Property = *It;
28 
29             Property->EmitReferenceInfo(*this, 0, EncounteredStructProps);
30 
31         }
32 
33         if (GetSuperClass())
34 
35         {
36 
37             GetSuperClass()->AssembleReferenceTokenStream();
38 
39             if (!GetSuperClass()->ReferenceTokenStream.IsEmpty())
40 
41                 PrependStreamWithSuperClass(*GetSuperClass());
42 
43         }
44 
45         else
46 
47             UObjectBase::EmitBaseReferences(this);
48 
49         static const FName EOSDebugName("EOS");
50 
51         EmitObjectReference(0, EOSDebugName, GCRT_EndOfStream);
52 
53         ReferenceTokenStream.Shrink();
54 
55         ClassFlags |= CLASS_TokenStreamAssembled;
56 
57     }
58 
59 }

 

注意,我這裏省去了一些代碼,其它的大體的邏輯就是若是沒有建立token stream或者要強制建立(須要清空ReferenceTokenSteam),那麼就會遍歷自身的全部屬性,而後對每一個UProperty調用EmitReferenceInfo()函數,它是一個虛函數,不一樣的UProperty會實現它,若是它有父類(GetSuperClass()),那麼就會調用父類的AssembleReferenceTokenStream()並把父類的添加到數組的前面,同時會處理GCRT_EndofStream的特殊狀況,最後加上GCRT_EndOfStream到記號流裏面去,並設置CLASS_TokenStreamAssembled標記。

下面咱們來看一個UObjectProperty::EmitReferenceInfo的實現,其它的UArrayProperty、UStructProperty等讀者可自行查看。

 
 1 /**
 2 
 3 * Emits tokens used by realtime garbage collection code to passed in OwnerClass' ReferenceTokenStream. The offset emitted is relative
 4 
 5 * to the passed in BaseOffset which is used by e.g. arrays of structs.
 6 
 7 */
 8 
 9 void UObjectProperty::EmitReferenceInfo(UClass& OwnerClass, int32 BaseOffset, TArray<const UStructProperty*>& EncounteredStructProps)
10 
11 {
12 
13     FGCReferenceFixedArrayTokenHelper FixedArrayHelper(OwnerClass, BaseOffset + GetOffset_ForGC(), ArrayDim, sizeof(UObject*), *this);
14 
15     OwnerClass.EmitObjectReference(BaseOffset + GetOffset_ForGC(), GetFName(), GCRT_Object);
16 
17 }

 

 

FGCReferenceProcessor

處理由TFastReferenceCollector查找到的UObject引用。

上面的流程圖中提到了簇的概念,那麼它是用來幹什麼的呢,咱們上面說過它是爲了提升GC性能的。咱們接下來就來看下簇的概念。

 

下面咱們來看一下UObject的繼承關係,其中跟Cluster相關的幾個函數在UObjectBaseUtility中,以下圖所示:

能夠看到,它們都是虛函數,能夠被重載,目前來看能夠做爲簇根(CanBeClusterRoot)的只有UMaterial和UParticleSystem這兩個類,而基本上全部的類均可以在簇中(CanBeInCluster),而建立簇是經過CreateCluster來完成的,固然建立簇須要必定的條件,好比咱們的CreateClusterFromPackage的函數定義爲:

 1 /** Looks through objects loaded with a package and creates clusters from them */
 2 
 3 void CreateClustersFromPackage(FLinkerLoad* PackageLinker)
 4 
 5 {    
 6 
 7     if (FPlatformProperties::RequiresCookedData() && !GIsInitialLoad && GCreateGCClusters && !GUObjectArray.IsOpenForDisregardForGC())
 8 
 9     {
10 
11         check(PackageLinker);
12 
13  
14 
15         for (FObjectExport& Export : PackageLinker->ExportMap)
16 
17         {
18 
19             if (Export.Object && Export.Object->CanBeClusterRoot())
20 
21             {
22 
23                 Export.Object->CreateCluster();
24 
25             }
26 
27         }
28 
29     }
30 
31 }

 

FPlatformProperties::RequiresCookedData()表明須要cook好的數據,因此編輯器模式下不會使用簇來GC。

接下來咱們看一下CreateCluster()函數的定義:

 1 void UObjectBaseUtility::CreateCluster()
 2 
 3 {
 4 
 5     FUObjectItem* RootItem = GUObjectArray.IndexToObject(InternalIndex);
 6 
 7     if (RootItem->GetOwnerIndex() != 0 || RootItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
 8 
 9     {
10 
11         return;
12 
13     }
14 
15     // If we haven't finished loading, we can't be sure we know all the references
16 
17     check(!HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad));
18 
19     // Create a new cluster, reserve an arbitrary amount of memory for it.
20 
21     FUObjectCluster* Cluster = new FUObjectCluster;
22 
23     Cluster->Objects.Reserve(64);
24 
25  
26 
27     // Collect all objects referenced by cluster root and by all objects it's referencing
28 
29     FClusterReferenceProcessor Processor(InternalIndex, *Cluster);
30 
31     TFastReferenceCollector<FClusterReferenceProcessor, TClusterCollector<FClusterReferenceProcessor>, FClusterArrayPool, true> ReferenceCollector(Processor, FClusterArrayPool::Get());
32 
33     TArray<UObject*> ObjectsToProcess;
34 
35     ObjectsToProcess.Add(static_cast<UObject*>(this));
36 
37     ReferenceCollector.CollectReferences(ObjectsToProcess, true);
38 
39     if (Cluster->Objects.Num())
40 
41     {
42 
43         // Add new cluster to the global cluster map.
44 
45         GUObjectClusters.Add(InternalIndex, Cluster);
46 
47         check(RootItem->GetOwnerIndex() == 0);
48 
49         RootItem->SetFlags(EInternalObjectFlags::ClusterRoot);
50 
51     }
52 
53     else
54 
55     {
56 
57         delete Cluster;
58 
59     }
60 
61 }

 

能夠看到它也使用了TFastReferenceCollector,只不過此次的模板參數爲FClusterReferenceProssor和TCusterCollector,這裏咱們就不展開去講了,讀者能夠自行閱讀代碼。

FGCCollector

這個類以下圖所示是繼承自FReferenceCollector,HandleObjectReference()和HandleObjectReferences()都調用了FGCReferenceProcessor的HandleObjectReference()方法來進行UObject的可達性分析。

FGCCollector的UML繼承關係圖以下所示:

清掃階段(Sweep)

前面,咱們通過了標記過程,那些標記了不可達標記的物體能夠進行刪除了,爲了減小卡頓,虛幻4加入了增量清除的概念(IncrementalPurgeGarbage()函數),就是一次刪除只佔用固定的時間片,固然,若是是編譯器狀態或者是強制徹底清除(好比下一次GC了,可是上一次增量清除尚未完成,那麼就會強制清除)。

IncrementalPurgeGarbage()函數的大致流程以下圖所示:

還記得咱們前面說的虛幻4使用簇來提升GC的效率嗎,下面是CollectGargageInternal函數中的一段,這個時候已經完成了可達性分析,代碼以下所示:

 1 for (FRawObjectIterator It(true); It; ++It)
 2 
 3 {
 4 
 5     FUObjectItem* ObjectItem = *It;
 6 
 7     if (ObjectItem->IsUnreachable())
 8 
 9     {
10 
11         if ((ObjectItem->GetFlags() & EInternalObjectFlags::ClusterRoot) == EInternalObjectFlags::ClusterRoot)// Nuke the entire cluster
12 
13         {
14 
15             ObjectItem->ClearFlags(EInternalObjectFlags::ClusterRoot | EInternalObjectFlags::NoStrongReference);
16 
17             const int32 ClusterRootIndex = It.GetIndex();
18 
19             FUObjectCluster* Cluster = GUObjectClusters.FindChecked(ClusterRootIndex);
20 
21             for (int32 ClusterObjectIndex : Cluster->Objects)
22 
23             {
24 
25                 FUObjectItem* ClusterObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ClusterObjectIndex);
26 
27                 ClusterObjectItem->ClearFlags(EInternalObjectFlags::NoStrongReference);
28 
29                 ClusterObjectItem->SetOwnerIndex(0);
30 
31                 if (!ClusterObjectItem->HasAnyFlags(EInternalObjectFlags::ReachableInCluster))
32 
33                 {
34 
35                     ClusterObjectItem->SetFlags(EInternalObjectFlags::Unreachable);
36 
37                     if (ClusterObjectIndex < ClusterRootIndex)
38 
39                     {
40 
41                         UObject* ClusterObject = (UObject*)ClusterObjectItem->Object;
42 
43                         ClusterObject->ConditionalBeginDestroy();
44 
45                     }
46 
47                 }
48 
49             }
50 
51             delete Cluster;
52 
53             GUObjectClusters.Remove(ClusterRootIndex);
54 
55         }
56 
57     }
58 
59 }

 

 

能夠看到,若是UObject帶有EInternalObjectFlags::ClusterRoot且不可達,那麼它會直接把上面的UObject(Cluster->Objects)符合條件的進行銷燬,而且把當前簇刪除掉。

總結

到此爲止,咱們對虛幻4中的垃圾回收進行了大概的講解,知道了它的GC是追蹤式、非實時、精確式,非漸近、增量回收(時間片),先標記後回收的過程,爲了提升效率和減小回收過程當中的卡頓,能夠作到並行標記和增量回收以及經過簇來提升回收的效率等。這篇文章只能給你一個大概的瞭解,若是想要清楚其中的細節,看代碼是免不了的。另外中間有錯誤的地方,若是讀者發現也請指正。歡迎你們留言討論。

參考文獻

  1. https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8)
  2. http://www.cnblogs.com/superjt/p/5946059.html
相關文章
相關標籤/搜索