参考:

  1. https://zhuanlan.zhihu.com/p/459287914#Collectreferences

前言

标记过程共分为两个阶段:标记对象可达性分析。对应到具体的函数就是 MarkObjectsAsUnreachable()PerformReachabilityAnalysisOnObjectsInternal(),上一节我们分析了前者,本节就来分析第二个函数。

PerformReachabilityAnalysis() 的执行逻辑

在分析该函数之前需要再理一理逻辑。

不知道大家还记不记得,CollectGarbageInternal() 最先调用的就是 PerformReachabilityAnalysis() 来执行 “标记-清扫” 中的标记阶段。我们先回顾一下这部分的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Performs reachability analysis.
*
* @param KeepFlags Objects with these flags will be kept regardless of being referenced or not
*/
void PerformReachabilityAnalysis(EObjectFlags KeepFlags, const EFastReferenceCollectorOptions InOptions)
{

// ...

/** Growing array of objects that require serialization */
FGCArrayStruct* ArrayStruct = FGCArrayPool::Get().GetArrayStructFromPool();
TArray<UObject*>& ObjectsToSerialize = ArrayStruct->ObjectsToSerialize;

// Reset object count.
GObjectCountDuringLastMarkPhase.Reset();

// Make sure GC referencer object is checked for references to other objects even if it resides in permanent object pool
if (FPlatformProperties::RequiresCookedData() && FGCObject::GGCObjectReferencer && GUObjectArray.IsDisregardForGC(FGCObject::GGCObjectReferencer))
{
ObjectsToSerialize.Add(FGCObject::GGCObjectReferencer);
}

{
const double StartTime = FPlatformTime::Seconds();
// Mark phase doesn't care about PendingKill being enabled or not so there's just fewer compiled in functions
const EFastReferenceCollectorOptions OptionsForMarkPhase = InOptions & ~EFastReferenceCollectorOptions::WithPendingKill;
(this->*MarkObjectsFunctions[GetGCFunctionIndex(OptionsForMarkPhase)])(ObjectsToSerialize, KeepFlags);
UE_LOG(LogGarbage, Verbose, TEXT("%f ms for MarkObjectsAsUnreachable Phase (%d Objects To Serialize)"), (FPlatformTime::Seconds() - StartTime) * 1000, ObjectsToSerialize.Num());
}

{
const double StartTime = FPlatformTime::Seconds();
PerformReachabilityAnalysisOnObjects(ArrayStruct, InOptions);
UE_LOG(LogGarbage, Verbose, TEXT("%f ms for Reachability Analysis"), (FPlatformTime::Seconds() - StartTime) * 1000);
}

// ...

}

首先,申请了一个名为 ArrayStruct 的结构体,该结构体就是用于存放所有可达的对象的;接下来,在标记对象的阶段,将 ArrayStruct->ObjectsToSerialize 作为引用传递给了 MarkObjectsAsUnreachable() 函数,对应这一句:

1
(this->*MarkObjectsFunctions[GetGCFunctionIndex(OptionsForMarkPhase)])(ObjectsToSerialize, KeepFlags);

标记对象阶段执行完成后,ObjectsToSerialize 就存放了所有的可达对象信息,由于 ObjectsToSerializeArrayStruct 的成员变量,所以信息也等同于存放在 ArrayStruct 中。

接下来就进入了可达性分析阶段:

1
2
3
4
5
6
PerformReachabilityAnalysisOnObjects(ArrayStruct, InOptions);

virtual void PerformReachabilityAnalysisOnObjects(FGCArrayStruct* ArrayStruct, const EFastReferenceCollectorOptions InOptions) override
{
(this->*ReachabilityAnalysisFunctions[GetGCFunctionIndex(InOptions)])(ArrayStruct);
}

PerformReachabilityAnalysisOnObjects() 只是一层包装,会将 ArrayStruct 等参数转发给 ReachabilityAnalysisFunctions 也即 PerformReachabilityAnalysisOnObjectsInternal()

PerformReachabilityAnalysisOnObjectsInternal()

该函数也只是做了一些参数转发的工作。具体而言,会先构造一个 TFastReferenceCollector 类型的对象,再调用该对象的 CollectReferences() 成员函数。 同时,会将 ArrayStruct 转发给该函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <EFastReferenceCollectorOptions CollectorOptions>
void PerformReachabilityAnalysisOnObjectsInternal(FGCArrayStruct* ArrayStruct)
{
TRACE_CPUPROFILER_EVENT_SCOPE(PerformReachabilityAnalysisOnObjectsInternal);
FGCReferenceProcessor<CollectorOptions> ReferenceProcessor;
// NOTE: we want to run with automatic token stream generation off as it should be already generated at this point,
// BUT we want to be ignoring Noop tokens as they're only pointing either at null references or at objects that never get GC'd (native classes)
TFastReferenceCollector<
FGCReferenceProcessor<CollectorOptions>,
FGCCollector<CollectorOptions>,
FGCArrayPool,
CollectorOptions
> ReferenceCollector(ReferenceProcessor, FGCArrayPool::Get());
ReferenceCollector.CollectReferences(*ArrayStruct);
}

我们先不考虑 TFastReferenceCollectorFGCReferenceProcessorFGCCollector 之间的关系,先从函数的作用开始分析起。

该函数也分了多线程与单线程两套处理方式,而它们最终都会调用一个名为 ProcessObjectArray() 的函数来完成后续的工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Performs reachability analysis.
*
* @param ObjectsToCollectReferencesFor List of objects which references should be collected
* @param bForceSingleThreaded Collect references on a single thread
*/
void CollectReferences(FGCArrayStruct& ArrayStruct)
{
TArray<UObject*>& ObjectsToCollectReferencesFor = ArrayStruct.ObjectsToSerialize;
if (ObjectsToCollectReferencesFor.Num())
{
if (!IsParallel())
{
FGraphEventRef InvalidRef;
ProcessObjectArray(ArrayStruct, InvalidRef);
}
else
{
// ...
}
}
}

TokenStream

在具体分析 ProcessObjectArray() 之前,还需要了解 TokenStream 这个概念。FGCReferenceTokenStream 是一个包含了多个 FGCReferenceInfo 的数组。GCReferenceInfo 定义在 GarbageCollection.h 中,是一个用于辅助 GC 分析对象的引用的数据结构。内部包含了一些重要属性,如返回深度,引用类型,偏移量等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/** 
* Convenience struct containing all necessary information for a reference.
*/
struct FGCReferenceInfo
{
/**
* Constructor
*
* @param InType type of reference
* @param InOffset offset into object/ struct
*/
FORCEINLINE FGCReferenceInfo(EGCReferenceType InReferenceType, uint32 InOffset)
: ReturnCount(0)
, Type(InReferenceType)
, Offset(InOffset)
{
checkf(InReferenceType != GCRT_None && InReferenceType <= 0x1F, TEXT("Invalid GC Token Reference Type (%d)"), (uint32)InReferenceType);
checkf((InOffset & ~0x7FFFF) == 0, TEXT("Invalid GC Token Offset (%d), max is %d"), InOffset, 0x7FFFF);
}
/**
* Constructor
*
* @param InValue value to set union mapping to a uint32 to
*/
FORCEINLINE FGCReferenceInfo( uint32 InValue )
: Value( InValue )
{}
/**
* uint32 conversion operator
*
* @return uint32 value of struct
*/
FORCEINLINE operator uint32() const
{
return Value;
}

/** Mapping to exactly one uint32 */
union
{
/** Mapping to exactly one uint32 */
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 */
uint32 Type : 5; // The number of bits needs to match TFastReferenceCollector::FStackEntry::ContainerHelperType
/** Offset into struct/ object */
uint32 Offset : 19;
};
/** uint32 value of reference info, used for easy conversion to/ from uint32 for token array */
uint32 Value;
};

/** End of token stream token */
static const FGCReferenceInfo EndOfStreamToken;
};

Token,准确的说是 ReferenceToken,翻译为引用记号流。引擎在启动时,会把代码中的各种类的类型信息收集好(也就是通常说的“反射”)。类型信息包括很多东西:比如说这个类里有什么变量,有什么函数,这个类的尺寸大小,是否是动态类等等。那哪个类型信息会在垃圾回收中用到呢?回想一下,UE 垃圾回收的原理:从已有的对象出发,查询这些对象分别引用了什么对象,从而构建出一个引用树(也可以称之为“可达的对象树”)。既然是引用别的对象,那么答案就很显然了,是使用了类型信息里的 “这个类有哪些变量” 这一信息。

如果在进行可达性分析的时候,去查询这个对象保存了对哪些变量的引用,固然可以。但是,既然我们只需要用到“这个类有哪些变量”这一信息,那么去访问一整个UObject,自然就需要为我们用不到的信息而支付更多的访问代价。

垃圾回收作为 UE 的一个底层系统,每时每刻都在运转的系统,它的效率提升将会直接给游戏的性能带来巨大的影响。因此 UE 在这里非常的“抠门”——它要用什么信息,就坚决只查询什么信息。于是 ReferenceToken 就应运而生了。早在引擎启动的时候,就会为每一个类都生成一份引用记号流(ReferenceToken),里面保存了这个类有哪些变量,因此在进行可达性分析的时候,只需要对ReferenceToken进行分析就可以了。

Token和一个完整的UObject,体积相差了十倍有余。它被UE用奇技淫巧组装成了一个4字节的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** Mapping to exactly one uint32 */
union
{
/** Mapping to exactly one uint32 */
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 */
uint32 Type : 5; // The number of bits needs to match TFastReferenceCollector::FStackEntry::ContainerHelperType
/** Offset into struct/ object */
uint32 Offset : 19;
};
/** uint32 value of reference info, used for easy conversion to/ from uint32 for token array */
uint32 Value;
};

这部分内容与 AssembleReferenceTokenStream() 函数展开了说也十分复杂,这里我们只需要这样简单理解:ReferenceToken 是 UE 为了减少访问 UObject 的开销设计出的一种结构,包含了类中某个属性的引用类型、偏移量等信息。而 TokenStream 则是一个类中所有纳入 GC 系统的属性的 ReferenceToken 的集合,可以通过 TokenStream 中记录的偏移量轻松地访问到某个对象的指定属性,而不用访问 UObject 自身。

ProcessObjectArray()

好,现在压力给到 ProcessObjectArray(),还是先看一下函数注释与签名:

1
2
3
4
5
6
7
/**
* Traverses UObject token stream to find existing references
*
* @param InObjectsToSerializeArray Objects to process
* @param MyCompletionGraphEvent Task graph event
*/
void ProcessObjectArray(FGCArrayStruct& InObjectsToSerializeStruct, const FGraphEventRef& MyCompletionGraphEvent)

可以看出该函数的作用就是遍历现有的 UObjecttokenstream 以找到更多的引用。已知在 MarkObjectsAsUnreachable() 执行完后,我们已经将所有纳入 GC 系统的对象都标记了可达/不可达,那为什么还要继续进行遍历呢?

举个例子,比如现在场景中有一个类型为 A 的对象 a,该对象继承了 UObject 因此会被纳入 GC 系统,在标记的第一阶段,也就是标记对象阶段,会对该对象进行标记,以判断是否需要被 GC。而如果 A 类型的对象中还引用着一个 B 类型的对象 b 呢?如果仅有 a 在引用 b,那么如果只回收了 a 的内存,b 岂不是就当场内存泄漏了。

因此,还需要从已有对象出发,判断更多的对象是否可达,并加入到列表中。这就是 ProcessObjectArray() 要完成的任务了。

我们可以结合函数的具体逻辑进行更清晰的理解,在上一篇博客 对于 MarkObjectsAsUnreachable() 函数的分析中可以看出,该函数对于对象的标记十分直接,仅仅判断了是否为根节点及对象是否携带特定标签的情况。因此,即使该函数也是遍历了一遍 GUObjectArray 但对于对象的标记是不全面的,有许多上述提到的被引用的情况不能被正确标记,因此才需要用到 PerformReachabilityAnalysis() 来具体分析上述的这些情况。

此外,从注释中我们可以知道,进行可达性分析时实际上是对 UObjectToken 进行分析。从上面的分析中我们知道,Token 是由 UPROPERTY 提供的信息而生成的一组精简的信息,指出了这个对象引用了哪些其它对象。通过分析 Token 而不是直接分析 UObject 本身,可以大大提高效率。

进入函数逻辑。

首先,定义了两个数组:

1
2
3
4
5
6
/** Growing array of objects that require serialization */
FGCArrayStruct& NewObjectsToSerializeStruct = *ArrayPool.GetArrayStructFromPool();

// Ping-pong between these two arrays if there's not enough objects to spawn a new task
TArray<UObject*>& ObjectsToSerialize = InObjectsToSerializeStruct.ObjectsToSerialize;
TArray<UObject*>& NewObjectsToSerialize = NewObjectsToSerializeStruct.ObjectsToSerialize;

其中,InObjectsToSerializeStruct 也即传入的 ArrayStruct,内部存放的是标记的第一阶段找到的所有可达对象。NewObjectsToSerialize 是用于存储在可达性分析循环中找到的更多的 要拿来垃圾回收的UObject,这两个数组会在循环结束后交换。并清空掉NewObjectsToSerialize。因此最终所有信息还是会回到 ObjectsToSerialize

1
2
3
4
5
6
7
8
9
10
else if (NewObjectsToSerialize.Num())
{
// Don't spawn a new task, continue in the current one
// To avoid allocating and moving memory around swap ObjectsToSerialize and NewObjectsToSerialize arrays
Exchange(ObjectsToSerialize, NewObjectsToSerialize);
// Empty but don't free allocated memory
NewObjectsToSerialize.SetNumUnsafeInternal(0);

CurrentIndex = 0;
}

函数会通过一个 while 循环来遍历 ObjectsToSerialize 中的元素。

首先会检查是否组装好了 TokenStream,否的话会重新调用 AssembleReferenceTokenStream() 进行 TokenStream 的组装,TokenStream 的作用在上文中已经分析过,是用于快速访问一个对象引用的其他对象的。

1
2
3
4
5
6
7
8
9
// Make sure that token stream has been assembled at this point as the below code relies on it.
if (!IsParallel() && CanAutogenerateTokenStream())
{
UClass* ObjectClass = CurrentObject->GetClass();
if (!ObjectClass->HasAnyClassFlags(CLASS_TokenStreamAssembled))
{
ObjectClass->AssembleReferenceTokenStream();
}
}

在获取到 TokenStream 后会对该对象的 TokenStream 进行解析,以拿到 ReferenceInfo 等信息。

1
2
const FGCReferenceInfo ReferenceInfo = TokenStream->AccessReferenceInfo(ReferenceTokenStreamIndex);
const EGCTokenType TokenType = TokenStream->GetTokenType();

随后会根据 ReferenceInfo.Type 的不同来分别处理,如 ObjectArrayObjectArrayStruct 等。ReferenceInfo.Type 是一个 EGCReferenceType 枚举类型的变量,该枚举类型也定义在 GarbageCollection.h 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Enum of different supported reference type tokens.
*/
enum EGCReferenceType
{
GCRT_None = 0,
GCRT_Object,
GCRT_Class,
GCRT_PersistentObject,
GCRT_ExternalPackage, // Specific reference type token for UObject external package
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_Optional,
GCRT_WeakObject,
GCRT_ArrayWeakObject,
GCRT_LazyObject,
GCRT_ArrayLazyObject,
GCRT_SoftObject,
GCRT_ArraySoftObject,
GCRT_Delegate,
GCRT_ArrayDelegate,
GCRT_MulticastDelegate,
GCRT_ArrayMulticastDelegate,
};

这里以 GCRT_Object 为例简单分析一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch(ReferenceInfo.Type)
{
case GCRT_Object:
case GCRT_Class:
{
// We're dealing with an object reference (this code should be identical to GCRT_NoopClass if ShouldProcessNoOpTokens())
UObject** ObjectPtr = (UObject**)(StackEntryData + ReferenceInfo.Offset);
UObject*& Object = *ObjectPtr;
TokenReturnCount = ReferenceInfo.ReturnCount;
ConditionalHandleTokenStreamObjectReference(NewObjectsToSerializeStruct, CurrentObject, Object, ReferenceTokenStreamIndex, TokenType, true);
}
break;
// ...
}

可以看出,这里的确是利用偏移量来直接获取到了一个对象引用的另一个对象,并且使用 ConditionalHandleTokenStreamObjectReference() 来对这个新找到的对象继续进行可达性标记。

1
2
3
4
5
6
7
FORCEINLINE void ConditionalHandleTokenStreamObjectReference(FGCArrayStruct& ObjectsToSerializeStruct, UObject* ReferencingObject, UObject*& Object, const int32 TokenIndex, const EGCTokenType TokenType, bool bAllowReferenceElimination)
{
if (IsObjectHandleResolved(*reinterpret_cast<FObjectHandle*>(&Object)))
{
ReferenceProcessor.HandleTokenStreamObjectReference(ObjectsToSerializeStruct, ReferencingObject, Object, TokenIndex, TokenType, bAllowReferenceElimination);
}
}

ConditionalHandleTokenStreamObjectReference() 会将参数转发给 ReferenceProcessor.HandleTokenStreamObjectReference()。中间又会经历很多次转发,最终会转发给 HandleObjectReference() 来进行真正的可达性分析。

HandleObjectReference()

1
2
3
4
5
6
7
8
/**
* Handles object reference, potentially NULL'ing
*
* @param Object Object pointer passed by reference
* @param ReferencingObject UObject which owns the reference (can be NULL)
* @param bAllowReferenceElimination Whether to allow NULL'ing the reference if RF_PendingKill is set
*/
FORCEINLINE void HandleObjectReference(FGCArrayStruct& ObjectsToSerializeStruct, const UObject * const ReferencingObject, UObject*& Object, const int32 TokenIndex, const EGCTokenType TokenType, const bool bAllowReferenceElimination)

从函数的签名可以看出,ReferencingObject 代表引用者(也就是 ObjectsToSerialize 中本身就有的对象),Object 是被引用者,就是类似于 ReferencingObject 的成员变量等的变量,该函数的作用就是将可达的 Object 放入 ObjectsToSerializeStruct 中。

函数内部的处理分为多种情况:

1
2
const int32 ObjectIndex = GUObjectArray.ObjectToIndex(Object);
FUObjectItem* ObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ObjectIndex);

这里的 ObjectItem 就是通过 GUObjectArray 及下标获取到的真正的被引用的对象。

  1. 首先,如果被引用的对象已经被标识为 Garbage,在 bAllowReferenceElimination == true 时会直接将其替换为空指针。此外还会根据其他配置做进一步的处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // Check for garbage objects but skip objects that were already marked as persistent garbage
    if (!IsWithPendingKill() && ObjectItem->HasAnyFlags(EInternalObjectFlags::Garbage))
    {
    if (bAllowReferenceElimination)
    {
    // To avoid content changes, allow reference elimination inside of Blueprints
    if (ReferencingObject && TokenType == EGCTokenType::NonNative)
    {
    Object = nullptr;
    return;
    }

    if (bGarbageReferenceTrackingEnabled && !ObjectItem->HasAnyFlags(EInternalObjectFlags::PersistentGarbage))
    {
    HandleGarbageReference(ObjectsToSerializeStruct, ReferencingObject, Object, TokenIndex);
    }
    }
    else if (bGarbageReferenceTrackingEnabled)
    {
    // This object is being referenced by a persistent reference which means it wouldn't have been GC'd anyway
    // so no need to track this object
    ObjectItem->ThisThreadAtomicallySetFlag(EInternalObjectFlags::PersistentGarbage);
    }
    }
  2. 如果 ObjectItem 已经被标记为 IsPendingKill(),则也会被清空。

    1
    2
    3
    4
    5
    6
    7
    8
    // Remove references to pending kill objects if we're allowed to do so.
    if (IsWithPendingKill() && ObjectItem->IsPendingKill() && bAllowReferenceElimination)
    {
    checkSlow(ObjectItem->GetOwnerIndex() <= 0);

    // Null out reference.
    Object = nullptr;
    }
  3. 如果我们引用的 Object 被标记为不可达,那么这里需要将其变为可达。

    如果引用的 Object 是一个非 ClusterRoot 对象,那么把它加入到 ObjectsToSerialize 中,后续会递归处理他。如果是一个 ClusterRoot 对象,则需要把该 Cluster 的所有对象全部变为可达。

    这里也分了单/多线程,但核心逻辑都是一样的,所以只贴出了多线程情况的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // Add encountered object reference to list of to be serialized objects if it hasn't already been added.
    else if (ObjectItem->IsUnreachable())
    {
    if (IsParallel())
    {
    // Mark it as reachable.
    if (ObjectItem->ThisThreadAtomicallyClearedRFUnreachable())
    {

    // ...

    // Objects that are part of a GC cluster should never have the unreachable flag set!
    checkSlow(ObjectItem->GetOwnerIndex() <= 0);

    if (!IsWithClusters() || !ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
    {
    // Add it to the list of objects to serialize.
    ObjectsToSerialize.Add(Object);
    }
    else
    {
    // This is a cluster root reference so mark all referenced clusters as reachable
    MarkReferencedClustersAsReachable(ObjectItem->GetClusterIndex(), ObjectsToSerialize);
    }
    }
    }
    else
    {
    // ...
    }
    }
  4. 如果我们引用的 ObjectCluster 的普通成员,说明该 Cluster 需要全部被标记为可达。

    这里先把该 ClusterClusterRoot 标记为可达,之后再根据ClusterRoot将该 Cluster 全部标记为可达。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    else if (IsWithClusters() && (ObjectItem->GetOwnerIndex() > 0 && !ObjectItem->HasAnyFlags(EInternalObjectFlags::ReachableInCluster)))
    {
    bool bNeedsDoing = true;
    if (IsParallel())
    {
    bNeedsDoing = ObjectItem->ThisThreadAtomicallySetFlag(EInternalObjectFlags::ReachableInCluster);
    }
    else
    {
    ObjectItem->SetFlags(EInternalObjectFlags::ReachableInCluster);
    }
    if (bNeedsDoing)
    {
    // Make sure cluster root object is reachable too
    const int32 OwnerIndex = ObjectItem->GetOwnerIndex();
    FUObjectItem* RootObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(OwnerIndex);
    checkSlow(RootObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot));
    if (IsParallel())
    {
    if (RootObjectItem->ThisThreadAtomicallyClearedRFUnreachable())
    {
    // Make sure all referenced clusters are marked as reachable too
    MarkReferencedClustersAsReachable(RootObjectItem->GetClusterIndex(), ObjectsToSerialize);
    }
    }
    else if (RootObjectItem->IsUnreachable())
    {
    RootObjectItem->ClearFlags(EInternalObjectFlags::Unreachable);
    // Make sure all referenced clusters are marked as reachable too
    MarkReferencedClustersAsReachable(RootObjectItem->GetClusterIndex(), ObjectsToSerialize);
    }
    }
    }

到此为止,HandleObjectReference() 也就分析完毕了。

小结

本节介绍了标记阶段的第二个函数:PerformReachabilityAnalysisOnObjectsInternal(),该函数的作用为从已有的可达对象出发,判断更多的对象是否可达,当这个函数执行完后,也就意味着 “标记-清扫” 的 “标记” 部分已经完成,接下来就是清扫的工作了。