UE4 GC原理

UObject概况

在开始之前,需要先介绍一下UE4是怎样管理UObject的,UObject其实并不是最上层的基类,UE4可能是觉得代码都写在UObject里太多了,看起来比较麻烦,所以就让UObject还继承着别的基类,这样可以把不同代码写到不同的基类里。其中最上层的基类是UObjectBase,他在创建的时候会把自己交给UE4的两个全局容器来管理,在销毁的时候把自己从管理自己的容器中移除

  • GUObjectArray : 类型是 FUObjectArray
  • FUObjectHashTables : FUObjectHashTables::Get()

UObject 继承关系

class UObject : public UObjectBaseUtility
{

}

class UObjectBaseUtility : public UObjectBase
{

}

要防止对象被GC,有4种方式:

  • 作为成员变量并标记为UPROPERTY();
  • 创建对象后 AddToRoot() ;(退出游戏时需要RemoveFromRoot())
  • FStreamableManager Load资源时,bManageActiveHandle 设置为true;
  • FGCObjectScopeGuard 在指定代码区域内保持对象;

UObject 创建时,会将自己添加到两个全局容器中:


UObjectBase::UObjectBase(UClass* InClass, EObjectFlags InFlags,
    EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)
:   ObjectFlags         (InFlags)
,   InternalIndex       (INDEX_NONE)
,   ClassPrivate        (InClass)
,   OuterPrivate        (InOuter)
{
    check(ClassPrivate);
    // Add to global table.
    AddObject(InName, InInternalFlags);
}

void UObjectBase::AddObject(FName InName, EInternalObjectFlags InSetInternalFlags)
{
    // 把自己添加到 UObjectArray 里,并且为 Object 分配 InternalIndex
    AllocateUObjectIndexForCurrentThread(this);
    check(InName != NAME_None && InternalIndex >= 0);

    // 把自己添加到 FUObjectHashTables 中
    HashObject(this);
}

FUObjectArray 介绍

FUObjectArray 基本代码结构

class FUObjectArray
{
private:
    /** First index into objects array taken into account for GC.*/
    int32 ObjFirstGCIndex;
    /** Index pointing to last object created in range disregarded for GC.*/
    int32 ObjLastNonGCIndex;

    typedef FChunkedFixedUObjectArray TUObjectArray;
    TUObjectArray ObjObjects;
};

class FChunkedFixedUObjectArray
{
    enum
    {
        NumElementsPerChunk = 64 * 1024,
    };

    // Master table to chunks of pointers 
    FUObjectItem** Objects;
    // If requested, a contiguous memory where all objects are allocated
    FUObjectItem* PreAllocatedObjects;
    // Maximum number of elements : 64 * 1024
    int32 MaxElements;
    // Number of elements we currently have 
    int32 NumElements;
    // Maximum number of chunks
    // Max UObject / NumElementsPerChunk
    // 25165824 = 24 * 1024 * 1024
    // 384
    int32 MaxChunks;
    /** Number of chunks we currently have **/
    int32 NumChunks;
}

FUObjectArray 会根据当前已有的 UObject 个数创建 Chunk 数组,每个 Chunk 可以容纳 64 * 1024 个对象。UObject->InternalIndex 其实就是表示该对象在 FUObjectArray 上的位置

Index = Object->InternalIndex;
// NumElementsPerChunk 是个常量 64 * 1024
const int32 ChunkIndex = Index / NumElementsPerChunk;
const int32 WithinChunkIndex = Index % NumElementsPerChunk;
void AllocateUObjectIndexForCurrentThread(UObjectBase* Object)
{
    GUObjectArray.AllocateUObjectIndex(Object);
}

void FUObjectArray::AllocateUObjectIndex(UObjectBase* Object, 
    bool bMergingThreads /*= false*/)
{

    // 根据当前已经有的 UObject个数,计算需要的 Chunk 个数
    // 如果 Chuck满了,则分配新的 Chuck 
    // 每个 Chunk 可以放 64 * 1024 个UObject

    // 空余的Index :对象销毁的时候,会把自身的 Index
    //              放到 ObjAvailableList 中
    int32* AvailableIndex = ObjAvailableList.Pop();
    if (AvailableIndex)
    {
        Index = (int32)(uintptr_t)AvailableIndex;
        check(ObjObjects[Index].Object==nullptr);
    }
    else
    {
        Index = ObjObjects.AddSingle();
    }

    // 给 FUObjectItem Object 指针 指向 新增的 UObject
    if (FPlatformAtomics::InterlockedCompareExchangePointer(
        (void**)&ObjObjects[Index].Object, Object, NULL) != NULL)
    {
        UE_LOG(LogUObjectArray, Fatal, TEXT("Unexpected 
        concurency while adding new object"));
    }

    Object->InternalIndex = Index;
}

// Object 销毁
UObjectBase::~UObjectBase()
{
    // If not initialized, skip out.
    if( UObjectInitialized() && ClassPrivate && !GIsCriticalError )
    {
        // Validate it.
        check(IsValidLowLevel());
        check(GetFName() == NAME_None);
        GUObjectArray.FreeUObjectIndex(this);
    }
}

void FUObjectArray::FreeUObjectIndex(UObjectBase* Object)
{
    int32 Index = Object->InternalIndex;
    // At this point no two objects exist with the same index
    // so no need to lock here
    if (FPlatformAtomics::InterlockedCompareExchangePointer(
        (void**)&ObjObjects[Index].Object, NULL, Object) == NULL) 
    {
        UE_LOG(LogUObjectArray, Fatal, TEXT("Unexpected concurency 
            while adding new object"));
    }
    if (Index > ObjLastNonGCIndex && !GExitPurge)  
    {
        ObjAvailableList.Push((int32*)(uintptr_t)Index);
    }
}

FUObjectItem 结构

struct FUObjectItem
{
    // Pointer to the allocated object
    class UObjectBase* Object;
    // Internal flags
    int32 Flags;
    // UObject Owner Cluster Index
    int32 ClusterRootIndex;
    // Weak Object Pointer Serial number associated with the object
    int32 SerialNumber;
}

// Flags : 标记位
enum class EInternalObjectFlags : int32
{
    None = 0,
    //~ All the other bits are reserved, DO NOT ADD NEW FLAGS HERE!

    ///< External reference to object in cluster exists
    ReachableInCluster = 1 << 23, 
    ///< Root of a cluster
    ClusterRoot = 1 << 24, 
    ///< Native (UClass only). 
    Native = 1 << 25, 
    ///< Object exists only on a different thread than the game thread.
    Async = 1 << 26, 
    ///< Object is being asynchronously loaded.
    AsyncLoading = 1 << 27, 

    // mark
    ///< Object is not reachable on the object graph.
    Unreachable = 1 << 28, 
    ///< Objects that are pending destruction 
    //   (invalid for gameplay but valid objects)
    PendingKill = 1 << 29, 

    // mark
    ///< Object will not be garbage collected, even if unreferenced.
    RootSet = 1 << 30, 
    //~ UnusedFlag = 1 << 31,

    GarbageCollectionKeepFlags = Native | Async | AsyncLoading,

    //~ Make sure this is up to date!
    AllFlags = ReachableInCluster | ClusterRoot | Native | Async 
        | AsyncLoading | Unreachable | PendingKill | RootSet
};

UObject 析构时,会把自己从全局数组中删除,重新创建 UObject 时,原来删除的空位会被重新分配给新的对象,原来的下标会指向新的对象,为了防止业务保存原来的下标取错对象,FUObjectItem 中有一个唯一 ID: SerialNumber。 SerialNumber 是一个自增不重复的的ID,可以用来唯一标识一个 UObject

int32 FUObjectArray::AllocateSerialNumber(int32 Index)
{
    FUObjectItem* ObjectItem = IndexToObject(Index);
    checkSlow(ObjectItem);

    volatile int32 *SerialNumberPtr = &ObjectItem->SerialNumber;
    int32 SerialNumber = *SerialNumberPtr;
    if (!SerialNumber)
    {

        //////////////////////////////////////////////
        SerialNumber = MasterSerialNumber.Increment();
        //////////////////////////////////////////////

        UE_CLOG(SerialNumber <= START_SERIAL_NUMBER, LogUObjectArray, Fatal, 
            TEXT("UObject serial numbers overflowed (trying to 
            allocate serial number %d)."), SerialNumber);

        int32 ValueWas = FPlatformAtomics::InterlockedCompareExchange(
            (int32*)SerialNumberPtr, SerialNumber, 0);

        if (ValueWas != 0)
        {
            // someone else go it first, use their value
            SerialNumber = ValueWas;
        }
    }
    checkSlow(SerialNumber > START_SERIAL_NUMBER);
    return SerialNumber;
}

容器 FUObjectArray 初始化

在游戏启动时,会初始化全局容器 FUObjectArray

void UObjectBaseInit()
{
    int32 MaxUObjects = 2 * 1024 * 1024; // Default to ~2M UObjects
    if (FPlatformProperties::RequiresCookedData())
    {
        // Maximum number of UObjects in cooked game
        GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), 
            TEXT("gc.MaxObjectsInGame"), MaxUObjects, GEngineIni);
    }
    else
    {
#if IS_PROGRAM
        // Maximum number of UObjects for programs can be low
        MaxUObjects = 100000; // Default to 100K for programs
        GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), 
            TEXT("gc.MaxObjectsInProgram"), MaxUObjects, GEngineIni);
#else
        // Maximum number of UObjects in the editor
        GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), 
            TEXT("gc.MaxObjectsInEditor"), MaxUObjects, GEngineIni);
#endif
    }
    GUObjectArray.AllocateObjectPool(MaxUObjects, MaxObjectsNotConsideredByGC,
        bPreAllocateUObjectArray);
}

void FUObjectArray::AllocateObjectPool(int32 InMaxUObjects, 
    int32 InMaxObjectsNotConsideredByGC, bool bPreAllocateObjectArray)
{
    ObjObjects.PreAllocate(InMaxUObjects, bPreAllocateObjectArray);
}

void FChunkedFixedUObjectArray::PreAllocate(int32 InMaxElements, 
    bool bPreAllocateChunks)
{
    MaxChunks = InMaxElements / NumElementsPerChunk + 1;
    MaxElements = MaxChunks * NumElementsPerChunk;
    Objects = new FUObjectItem*[MaxChunks];
}

MaxObjects 配置如下:

Android 配置:

标记清除GC过程

GC 标记流程

入口为UObjectGlobals.h中定义的CollectGarbage()函数,如下:

  • 获取GC锁
  • 执行CollectGarbageInternal
  • 释放GC锁
void CollectGarbage(EObjectFlags KeepFlags, bool bPerformFullPurge)
{
    // No other thread may be performing UObject operations while we're running
    AcquireGCLock();

    // Perform actual garbage collection
    CollectGarbageInternal(KeepFlags, bPerformFullPurge);

    // Other threads are free to use UObjects
    ReleaseGCLock();
}

可达性分析:

void CollectGarbageInternal(EObjectFlags KeepFlags,
    bool bPerformFullPurge)
{
    // Perform reachability analysis.
    {
        const double StartTime = FPlatformTime::Seconds();
        FRealtimeGC TagUsedRealtimeGC;
        TagUsedRealtimeGC.PerformReachabilityAnalysis(KeepFlags, 
            bForceSingleThreadedGC, bWithClusters);

        UE_LOG(LogGarbage, Log, TEXT("%f ms for GC"), 
            (FPlatformTime::Seconds() - StartTime) * 1000);
    }
}

void PerformReachabilityAnalysis(EObjectFlags KeepFlags, 
    bool bForceSingleThreaded, bool bWithClusters)
{

    FGCArrayStruct* ArrayStruct = FGCArrayPool::Get().GetArrayStructFromPool();
    TArray<UObject*>& ObjectsToSerialize = ArrayStruct->ObjectsToSerialize;

    const double StartTime = FPlatformTime::Seconds();

    (this->*MarkObjectsFunctions[GetGCFunctionIndex(!bForceSingleThreaded,
        bWithClusters)])(ObjectsToSerialize, KeepFlags);

}

static FORCEINLINE int32 GetGCFunctionIndex(bool bParallel, bool bWithClusters)
{
    return (int32(bParallel) | (int32(bWithClusters) << 1));
}

class FRealtimeGC : public FGarbageCollectionTracer
{
    MarkObjectsFn MarkObjectsFunctions[4];
    ReachabilityAnalysisFn ReachabilityAnalysisFunctions[4];


    FRealtimeGC()
    {
        MarkObjectsFunctions[GetGCFunctionIndex(false, false)] = 
            &FRealtimeGC::MarkObjectsAsUnreachable<false, false>;
        MarkObjectsFunctions[GetGCFunctionIndex(true, false)] = 
            &FRealtimeGC::MarkObjectsAsUnreachable<true, false>;
        MarkObjectsFunctions[GetGCFunctionIndex(false, true)] = 
            &FRealtimeGC::MarkObjectsAsUnreachable<false, true>;
        MarkObjectsFunctions[GetGCFunctionIndex(true, true)] = 
            &FRealtimeGC::MarkObjectsAsUnreachable<true, true>;

        ReachabilityAnalysisFunctions[GetGCFunctionIndex(false, false)] = 
            &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal
            <EFastReferenceCollectorOptions::None | 
            EFastReferenceCollectorOptions::None>;
        ReachabilityAnalysisFunctions[GetGCFunctionIndex(true, false)] = 
            &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal
            <EFastReferenceCollectorOptions::Parallel | 
            EFastReferenceCollectorOptions::None>;
        ReachabilityAnalysisFunctions[GetGCFunctionIndex(false, true)] = 
            &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal
            <EFastReferenceCollectorOptions::None | 
            EFastReferenceCollectorOptions::WithClusters>;
        ReachabilityAnalysisFunctions[GetGCFunctionIndex(true, true)] = 
            &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal
            <EFastReferenceCollectorOptions::Parallel | 
            EFastReferenceCollectorOptions::WithClusters>;
    }
}

标记函数实现如下:
UE使用了簇(Cluster)来提高效率,Cluster 是一组 UObject ,在 GC 流程中被视为一个单一的单位,能加速 GC。Cluster 后面再仔细研究下。

/// 标记函数
template <bool bParallel, bool bWithClusters>
void MarkObjectsAsUnreachable(TArray<UObject*>& ObjectsToSerialize, 
    const EObjectFlags KeepFlags)
{
    const EInternalObjectFlags FastKeepFlags = 
        EInternalObjectFlags::GarbageCollectionKeepFlags;
    // 从全局数组 GUObjectArray 中获取需要 GC UObject 个数
    const int32 MaxNumberOfObjects = GUObjectArray.GetObjectArrayNum()
        - GUObjectArray.GetFirstGCIndex();
    // 计算每个 Work 线程需要处理的 UObject 个数
    const int32 NumThreads = FMath::Max(1, 
        FTaskGraphInterface::Get().GetNumWorkerThreads());
    const int32 NumberOfObjectsPerThread = (MaxNumberOfObjects / NumThreads) + 1;

    for (int32 ThreadIndex = 0; ThreadIndex < NumThreads; ++ThreadIndex)
    {
        ObjectsToSerializeArrays[ThreadIndex] = 
            FGCArrayPool::Get().GetArrayStructFromPool();
    }

    //  worker 线程分段对整个 GUObjectArray 处理
    ParallelFor(NumThreads, [ObjectsToSerializeArrays, &ClustersToDissolveList, 
        &KeepClusterRefsList, FastKeepFlags, KeepFlags, NumberOfObjectsPerThread, 
        NumThreads, MaxNumberOfObjects](int32 ThreadIndex)
    {
        int32 FirstObjectIndex = ThreadIndex * NumberOfObjectsPerThread 
            + GUObjectArray.GetFirstGCIndex();

        int32 NumObjects = (ThreadIndex < (NumThreads - 1)) ? 
            NumberOfObjectsPerThread : (MaxNumberOfObjects - 
            (NumThreads - 1) * NumberOfObjectsPerThread);

        int32 LastObjectIndex = FMath::Min(GUObjectArray.GetObjectArrayNum() - 1, 
            FirstObjectIndex + NumObjects - 1);

        int32 ObjectCountDuringMarkPhase = 0;
        TArray<UObject*>& LocalObjectsToSerialize = 
            ObjectsToSerializeArrays[ThreadIndex]->ObjectsToSerialize;

        for (int32 ObjectIndex = FirstObjectIndex; ObjectIndex <= LastObjectIndex; 
            ++ObjectIndex)
        {
            FUObjectItem* ObjectItem = 
                &GUObjectArray.GetObjectItemArrayUnsafe()[ObjectIndex];
            if (ObjectItem->Object)
            {
                UObject* Object = (UObject*)ObjectItem->Object;
                // Keep track of how many objects are around.
                ObjectCountDuringMarkPhase++;
                
                if (bWithClusters)
                {
                    ObjectItem->ClearFlags(
                        EInternalObjectFlags::ReachableInCluster);
                }

                // 1.如果一个object属于RootSet 不 GC
                // Object->AddToRoot()
                if (ObjectItem->IsRootSet())
                {
                    if (bWithClusters)
                    {
                        if (ObjectItem->HasAnyFlags(
                            EInternalObjectFlags::ClusterRoot) 
                            || ObjectItem->GetOwnerIndex() > 0)
                        {
                            KeepClusterRefsList.Push(ObjectItem);
                        }
                    }

                    LocalObjectsToSerialize.Add(Object);
                }
                // Regular objects or cluster root objects
                else if (!bWithClusters || ObjectItem->GetOwnerIndex() <= 0)
                {
                    bool bMarkAsUnreachable = true;
                    // 2.如果一个 object 有 Keep 标记位 不 GC
                    if (ObjectItem->HasAnyFlags(FastKeepFlags))
                    {
                        bMarkAsUnreachable = false;
                    }

                    else if (!ObjectItem->IsPendingKill() && 
                        KeepFlags != RF_NoFlags && Object->HasAnyFlags(KeepFlags))
                    {
                        bMarkAsUnreachable = false;
                    }
                    else if (ObjectItem->IsPendingKill() && bWithClusters && 
                        ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
                    {
                        ClustersToDissolveList.Push(ObjectItem);
                    }
                   
                    if (!bMarkAsUnreachable)
                    {
                        LocalObjectsToSerialize.Add(Object);

                        if (bWithClusters)
                        {
                            if (ObjectItem->HasAnyFlags(
                                EInternalObjectFlags::ClusterRoot))
                            {
                                KeepClusterRefsList.Push(ObjectItem);
                            }
                        }
                    }
                    else
                    {
                        // 不可达 设置 不可达的标记为
                        ObjectItem->SetFlags(EInternalObjectFlags::Unreachable);
                    }
                }
            }
        }

        GObjectCountDuringLastMarkPhase.Add(ObjectCountDuringMarkPhase);
    }, !bParallel);
}

所有收集到的不能被 GC 的 UObject 都会最终添加到 ObjectsToSerialize 中。然后会调用 ReachabilityAnalysisFunctions 数组中的函数分析:

void PerformReachabilityAnalysis(EObjectFlags KeepFlags, 
    bool bForceSingleThreaded, bool bWithClusters)
{

    FGCArrayStruct* ArrayStruct = FGCArrayPool::Get().GetArrayStructFromPool();
    TArray<UObject*>& ObjectsToSerialize = ArrayStruct->ObjectsToSerialize;

    /// step 1: 上面的可达性分析
    const double StartTime = FPlatformTime::Seconds();
    (this->*MarkObjectsFunctions[GetGCFunctionIndex(!bForceSingleThreaded, 
        bWithClusters)])(ObjectsToSerialize, KeepFlags);

    /// step 2:
    const double StartTime = FPlatformTime::Seconds();
    PerformReachabilityAnalysisOnObjects(ArrayStruct, 
        bForceSingleThreaded, bWithClusters);
}

收集引用信息

基础类概念介绍
先介绍一下ReferenceToken概念
在UObject体系中,每个类有一个UClass实例用于描述该类的反射信息,使用UProperty可描述每个类的成员变量,但在GC中如果直接遍历UProperty来扫描对象引用关系,效率会比较低(因为存在许多非Object引用型Property),所以UE创建了ReferenceToken,它是一组toke流,描述类中对象的引用情况。

下面代码中列举了引用的类型:

enum EGCReferenceType
{
    GCRT_None                   = 0,
    GCRT_Object,
    GCRT_Class,
    GCRT_PersistentObject,

    // Specific reference type token for UObject external package
    GCRT_ExternalPackage,       
    GCRT_ArrayObject,
    GCRT_ArrayStruct,
    GCRT_FixedArray,
    GCRT_AddStructReferencedObjects,
    GCRT_AddReferencedObjects,
    GCRT_AddTMapReferencedObjects,
    GCRT_AddTSetReferencedObjects,
    GCRT_AddFieldPathReferencedObject,
    GCRT_ArrayAddFieldPathReferencedObject,
    GCRT_EndOfPointer,
    GCRT_EndOfStream,
    GCRT_NoopPersistentObject,
    GCRT_NoopClass,
    GCRT_ArrayObjectFreezable,
    GCRT_ArrayStructFreezable,
    GCRT_WeakObject,
    GCRT_ArrayWeakObject,
    GCRT_LazyObject,
    GCRT_ArrayLazyObject,
    GCRT_SoftObject,
    GCRT_ArraySoftObject,
    GCRT_Delegate,
    GCRT_ArrayDelegate,
    GCRT_MulticastDelegate,
    GCRT_ArrayMulticastDelegate,
};

下面是 FGCReferenceInfo 类型定义

  • ReturnCount:返回的嵌套深度
  • Type:引用的类型,就是 EGCRefenceType
  • Offset:这个引用对应的属性在类中的地址偏移

UE巧妙的把这3个信息编码成了一个uint32,因此 FGCReferenceTokenStream 可以通过 TArray形式存储 tokens


struct FGCReferenceInfo
{
    union
    {
        struct
        {
            /** Return depth, e.g. 1 for last entry in an array, 
            2 for last entry in an array of structs of arrays, ... */
            uint32 ReturnCount  : 8;
            /** Type of reference */
            
            // The number of bits needs to match 
            // TFastReferenceCollector::FStackEntry::ContainerHelperType
            uint32 Type         : 5; 
            /** Offset into struct/ object */
            uint32 Offset       : 19;
        };
        /** uint32 value of reference info, used for easy conversion 
            to/ from uint32 for token array */
        uint32 Value;
    };
};


struct FGCReferenceTokenStream
{
    /** Token array */
    TArray<uint32> Tokens;
}

下图是我截图的一个堆栈:
这是一个 UMG 测试蓝图 WBP_TestForm_C 初始化 ReferenceTokenStream 时,断点处的内存信息,两个引用 Object 分别是 BtnBack 跟 ImageTest,然后每个 Object 在整个对象的内存空间的偏移地址为:1128 跟 1136,它们嵌套深度都是0。

  • Offset            : 1136 -> 0100 0111 0000
  • Type              : 1      -> 0 0001
  • ReturnCount  : 0      -> 0000 0000
void ProcessObjectArray(FGCArrayStruct& InObjectsToSerializeStruct, 
    const FGraphEventRef& MyCompletionGraphEvent)
{
    while (CurrentIndex < ObjectsToSerialize.Num())
    {
        CurrentObject = ObjectsToSerialize[CurrentIndex++];
        // Get pointer to token stream and jump to the start.
        FGCReferenceTokenStream* RESTRICT TokenStream = 
            &CurrentObject->GetClass()->ReferenceTokenStream;
        uint32 TokenStreamIndex = 0;
        // Keep track of index to reference info. Used to avoid LHSs.
        uint32 ReferenceTokenStreamIndex = 0;

        FStackEntry* RESTRICT StackEntry = Stack.GetData();

        // 对象的起始地址
        uint8* StackEntryData = (uint8*)CurrentObject;
        StackEntry->Data = StackEntryData;
        StackEntry->ContainerType = GCRT_None;
        StackEntry->Stride = 0;
        StackEntry->Count = -1;
        StackEntry->LoopStartIndex = -1;

        // Keep track of token return count in separate integer 
        // as arrays need to fiddle with it.
        int32 TokenReturnCount = 0;

        // Parse the token stream.
        while (true)
        {
            ////
            switch(ReferenceInfo.Type)
            {
                case GCRT_Object:
                case GCRT_Class:
                {
                    // 引用对象的地址: 起始地址 + Offset
                    UObject**   ObjectPtr = (UObject**)(StackEntryData + 
                        ReferenceInfo.Offset);

                    UObject*&   Object = *ObjectPtr;
                    TokenReturnCount = ReferenceInfo.ReturnCount;
                    ReferenceProcessor.HandleTokenStreamObjectReference(
                        NewObjectsToSerialize, CurrentObject, Object, 
                        ReferenceTokenStreamIndex, true);
                }
                break;
                case GCRT_ArrayObject:
                {
                    // We're dealing with an array of object references.
                    TArray<UObject*>& ObjectArray = *((TArray<UObject*>*)
                        (StackEntryData + ReferenceInfo.Offset));

                    TokenReturnCount = ReferenceInfo.ReturnCount;
                    for (int32 ObjectIndex = 0, ObjectNum = ObjectArray.Num(); 
                        ObjectIndex < ObjectNum; ++ObjectIndex)
                    {
                        ReferenceProcessor.HandleTokenStreamObjectReference(
                            NewObjectsToSerialize, CurrentObject, 
                            ObjectArray[ObjectIndex], 
                            ReferenceTokenStreamIndex, true);
                    }
                }
                break;
            }
        }
    }
}

最后调用到 HandleObjectReference 对引用的对象设置可达标记位,

FORCEINLINE void HandleObjectReference(TArray<UObject*>& ObjectsToSerialize, 
    const UObject * const ReferencingObject, UObject*& Object, 
    const bool bAllowReferenceElimination)
{
    const bool IsInPermanentPool = 
        GUObjectAllocator.ResidesInPermanentPool(Object);

    const int32 ObjectIndex = GUObjectArray.ObjectToIndex(Object);
    FUObjectItem* ObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ObjectIndex);
    // Remove references to pending kill objects if we're allowed to do so.
    if (ObjectItem->IsPendingKill() && bAllowReferenceElimination)
    {
        ///
    }
}

清理操作

标记阶段完成后,会进入清理阶段,收集所有不可达的 UObject 整理到全局列表 GUnreachableObjects

void CollectGarbageInternal(EObjectFlags KeepFlags, bool bPerformFullPurge)
{
    GatherUnreachableObjects(bForceSingleThreadedGC);
    NotifyUnreachableObjects(GUnreachableObjects);
}


bool UnhashUnreachableObjects(bool bUseTimeLimit, float TimeLimit)
{
    const double StartTime = FPlatformTime::Seconds();
    const int32 TimeLimitEnforcementGranularityForBeginDestroy = 10;
    int32 Items = 0;
    int32 TimePollCounter = 0;
    const bool bFirstIteration = (GUnrechableObjectIndex == 0);

    while (GUnrechableObjectIndex < GUnreachableObjects.Num())
    {
        FUObjectItem* ObjectItem = GUnreachableObjects[GUnrechableObjectIndex++];
        {
            UObject* Object = static_cast<UObject*>(ObjectItem->Object);
            FScopedCBDProfile Profile(Object);
            // Begin the object's asynchronous destruction.
            Object->ConditionalBeginDestroy();
        }
    }
}

/// GC 尝试
bool UObject::ConditionalBeginDestroy()
{
#if !UE_BUILD_SHIPPING
    if (DebugSpikeMarkAnnotation.Num() > 0)
    {
        if(!DebugSpikeMarkAnnotation.Get(this))
        {
            DebugSpikeMarkNames.Add(GetFullName());
        }
    }
#endif
    
    check(IsValidLowLevel());
    if( !HasAnyFlags(RF_BeginDestroyed) )
    {
        SetFlags(RF_BeginDestroyed);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
        checkSlow(!DebugBeginDestroyed.Contains(this));
        DebugBeginDestroyed.Add(this);
#endif

#if PROFILE_ConditionalBeginDestroy
        double StartTime = FPlatformTime::Seconds();
#endif

        BeginDestroy();

    }
}

最终完成 GC 清理。