前言

在前三节中,我们主要分析了 “标记-清扫” 中的标记阶段内容,并且我们知道了在 UE 中,标记阶段是分两步进行的,第一步会遍历现有对象,对一些诸如根节点的情况进行简单标记,而第二阶段则会执行更加细致的可达性分析,根据类中引用与被引用的关系来再次遍历对象,以进行完整的可达性标记。

UE 补完计划(二) GC-1
UE 补完计划(二) GC-2
UE 补完计划(二) GC-3

在正确标记了所有需要 GC 的对象后,接下来就该清理这些垃圾了,本节就来分析这部分的内容。

CollectGarbageInternal()

回到 CollectGarbageInternal() 函数,剩下的这部分内容就是与清扫相关的了。

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
FGCArrayPool& ArrayPool = FGCArrayPool::Get();
TArray<FGCArrayStruct*> AllArrays;
ArrayPool.GetAllArrayStructsFromPool(AllArrays);
// This needs to happen before clusters get dissolved otherwisise cluster information will be missing from history
ArrayPool.UpdateGCHistory(AllArrays);

// Reconstruct clusters if needed
if (GUObjectClusters.ClustersNeedDissolving())
{
const double StartTime = FPlatformTime::Seconds();
GUObjectClusters.DissolveClusters();
UE_LOG(LogGarbage, Log, TEXT("%f ms for dissolving GC clusters"), (FPlatformTime::Seconds() - StartTime) * 1000);
}

// Fire post-reachability analysis hooks
{
TRACE_CPUPROFILER_EVENT_SCOPE(BroadcastPostReachabilityAnalysis);
FCoreUObjectDelegates::PostReachabilityAnalysis.Broadcast();
}

{
ArrayPool.DumpGarbageReferencers(AllArrays);

GatherUnreachableObjects(!(Options & EFastReferenceCollectorOptions::Parallel));
NotifyUnreachableObjects(GUnreachableObjects);

// This needs to happen after NotifyGarbageReferencers and GatherUnreachableObjects since both can mark more objects as unreachable
ArrayPool.ClearWeakReferences(AllArrays);

// Now return arrays back to the pool and free some memory if requested
for (int32 Index = 0; Index < AllArrays.Num(); ++Index)
{
FGCArrayStruct* ArrayStruct = AllArrays[Index];
if (bPerformFullPurge
|| Index % 7 == 3) // delete 1/7th of them just to keep things from growing too much between full purges
{
ArrayPool.FreeArrayStruct(ArrayStruct);
}
else
{
ArrayPool.ReturnToPool(ArrayStruct);
}
}

// Make sure nothing will be using potentially freed arrays
AllArrays.Empty();

if (bPerformFullPurge || !GIncrementalBeginDestroyEnabled)
{
UnhashUnreachableObjects(/**bUseTimeLimit = */ false);
FScopedCBDProfile::DumpProfile();
}
}

// ...

关于 ArrayPool

如果大家仔细观察的话就会发现,GC 中用于统计全局可达/不可达对象等全局对象的大规模数组基本都是以 FGCArrayPool::Get().GetArrayStructFromPool() 这样的方式通过一个统一的接口申请到的。

而在 ProcessObjectArray() 的末尾,携带了所有可达对象信息的数组会被归还给 ArrayPool

1
ArrayPool.ReturnToPool(&NewObjectsToSerializeStruct);

在标记阶段执行完毕后,CollectGarbageInternal() 会再次从 ArrayPool 中重新获取这些数组,并做清扫处理。

1
2
3
FGCArrayPool& ArrayPool = FGCArrayPool::Get();
TArray<FGCArrayStruct*> AllArrays;
ArrayPool.GetAllArrayStructsFromPool(AllArrays);

GUnreachableObjects

接下来是关于不可达对象的收集以及一个事件的通知

1
2
GatherUnreachableObjects(!(Options & EFastReferenceCollectorOptions::Parallel));
NotifyUnreachableObjects(GUnreachableObjects);