虚幻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
相关文章
相关标签/搜索