🚯 Unreal 5 垃圾回收源码梳理
2025-06-05
为了使代码更加简洁易读,本文出现的 UE 源代码会去除掉输出日志的部分,用 //...log 来表示,除此以外,本文会用 ... 来表示省略的代码。
前言
垃圾回收概念
-
垃圾回收:垃圾回收或者说垃圾回收器是一段定期运行的程序。它会检查对象的存活状态,并释放死亡对象的内存。所谓“死亡”的对象,就是在程序中永远无法再被访问的对象,也是“垃圾回收”中的“垃圾”。 -
对象图(Objects Graph):对象图是指对象和对象的引用关系构成的有向图,它描述了对象之间的引用关系。 -
标记清扫(Mark-Sweep):是现今最常用的垃圾回收策略。它的基本思想是从一些特殊的“根对象”开始,根据对象之间的引用管线,完整地遍历对象图。所有被遍历对象会被打上“可达”标记。接着,再遍历一遍所有对象,如果一个对象没有被打上可达标记,我们认为它是死亡的。再标记清扫中,我们认为对象的“存活性”和“可达性”是等价的。根对象在 UE 中有 UWorld 等,也可以人为指定。 -
增量回收(Incremental):“增量回收”的思想是将一次垃圾回收分成多个小段在不同的时间执行,这样分散了一次垃圾回收的时长,减少了程序卡顿的可能性。增量回收也面临着挑战,例如对象图在运行时是变化的。增量回收需要一些异步安全机制,这些在下文中都会介绍。 -
全量回收(Full):全量回收是为了和增量回收区分而存在的名词,有了增量回收之后,之前的“一次进行全部回收”的行为就是全量回收。 -
对象簇(Cluster):是一种将强相关的对象放在一起的数据结构,用于减少对象图的遍历次数
章节安排
-
UE 5 垃圾回收编程范式 -
垃圾回收的触发机制 -
垃圾回收过程 -
引用与对象图
UE 5 垃圾回收编程范式
增量回收
// GarbageCollection.cpp
std::atomic<bool> GObjIncrementalPurgeIsInProgress = false;
// GarbageCollection.cpp
bool IsIncrementalPurgePending()
{
return GObjIncrementalPurgeIsInProgress || GObjPurgeIsRequired;
}
// GarbageCollection.cpp/IncrementalPurgeGarbage()
bTimeLimitReached = UnhashUnreachableObjects(bUseTimeLimit, TimeLimit);
多线程与异步优化
// GarbageCollection.cpp/GatherUnreachableObjects
ParallelFor( TEXT("GC.GatherUnreachable"), ...)
垃圾回收的触发机制
强制垃圾回收
// UnrealEngine.cpp
void UEngine::ForceGarbageCollection(bool bForcePurge/*=false*/)
{
TimeSinceLastPendingKillPurge = 1.0f + GetTimeBetweenGarbageCollectionPasses();
bFullPurgeTriggered = bFullPurgeTriggered || bForcePurge;
//...log
}
-
bForcePurge 表示这次强制回收是否是一次全量回收,与之相对的是增量回收 Incremental Purge -
TimeSinceLastPendingKillPurge 私有成员变量的作用是记录距离上一次清除待销毁的对象的时间,用于判断垃圾回收条件 -
GetTimeBetweenGarbageCollectionPasses() 用于获取两次垃圾回收之间的时间间隔 -
bFullPurgeTriggered 私有成员变量的作用是标记下一次垃圾回收是否是一次全量回收
条件垃圾回收
// UnrealEngine.cpp
void UEngine::ConditionalCollectGarbage()
{
...
const float TimeBetweenPurgingPendingKillObjects = GetTimeBetweenGarbageCollectionPasses(bHasPlayersConnected);
...
// Perform incremental purge update if it's pending or in progress.
else if (!IsIncrementalPurgePending()
// Purge reference to pending kill objects every now and so often.
&& (TimeSinceLastPendingKillPurge > TimeBetweenPurgingPendingKillObjects) && TimeBetweenPurgingPendingKillObjects > 0.f)
...
}
-
ConditionalCollectGarbage() 函数会在 UWord::Tick 中被每帧调用 -
GetTimeBetweenGarbageCollectionPasses() 函数有无参和有一个参的两个重载
情况 | 回收间隔默认值/s | 相关变量名 |
默认情况 | 60.0 | GTimeBetweenPurgingPendingKillObjects |
服务器且没有玩家 | 60.0 * 10.0 | GTimeBetweenPurgingPendingKillObjectsOnIdleServerMultiplier(无服务器时的间隔系数) |
低内存 | 30.0 | GLowMemoryMemoryThresholdMB(低内存阈值), GLowMemoryTimeBetweenPurgingPendingKillObjects(低内存时间隔) |
ConditionalCollectGarbage
详解
// UnrealEngine.cpp
void UEngine::ConditionalCollectGarbage()
{
if (GFrameCounter != LastGCFrame)
-
GFrameCounter 是引擎的全局帧号 -
LastGCFrame 是上一次触发垃圾回收的帧号
else if (IsIncrementalReachabilityAnalysisPending())
{
PerformIncrementalReachabilityAnalysis(GetReachabilityAnalysisTimeLimit());
}
-
IsIncrementalReachabilityAnalysisPending() 获取增量垃圾回收是否待执行
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_ConditionalCollectGarbage);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (CVarStressTestGCWhileStreaming.GetValueOnGameThread() && IsAsyncLoading())
{
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true);
}
else if (CVarForceCollectGarbageEveryFrame.GetValueOnGameThread())
{
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true);
}
else
#endif
{
EGarbageCollectionType ForceTriggerPurge = ShouldForceGarbageCollection();
#if WITH_VERSE_VM || defined(__INTELLISENSE__)
if (ForceTriggerPurge == EGarbageCollectionType::None && UE::GC::ShouldFrankenGCRun())
{
ForceTriggerPurge = EGarbageCollectionType::Incremental;
}
#endif
if (ForceTriggerPurge != EGarbageCollectionType::None)
{
ForceGarbageCollection(ForceTriggerPurge == EGarbageCollectionType::Full);
}
-
EGarbageCollectionType 枚举有三种值
if (bFullPurgeTriggered)
{
if (TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true))
{
ForEachObjectOfClass(UWorld::StaticClass(),[](UObject* World)
{
CastChecked<UWorld>(World)->CleanupActors();
});
bFullPurgeTriggered = false;
bShouldDelayGarbageCollect = false;
TimeSinceLastPendingKillPurge = 0.0f;
}
}
-
bFullPurgeTriggered 标记是否使用全量垃圾回收,在执行后被设为 false
else
{
const bool bTestForPlayers = IsRunningDedicatedServer();
bool bHasAWorldBegunPlay = false;
bool bHasPlayersConnected = false;
// Look for conditions in the worlds that would change the GC frequency
for (const FWorldContext& Context : WorldList) {
if (UWorld* World = Context.World()) {
if (World->HasBegunPlay()) {
bHasAWorldBegunPlay = true;
}
if (bTestForPlayers &&
World->NetDriver &&
World->NetDriver->ClientConnections.Num() > 0
) {
bHasPlayersConnected = true;
}
// If we found the conditions we wanted, no need to continue iterating
if (bHasAWorldBegunPlay &&
(!bTestForPlayers || bHasPlayersConnected)
){
break;
}
}
}
if (bHasAWorldBegunPlay)
{
TimeSinceLastPendingKillPurge += FApp::GetDeltaTime();
const float TimeBetweenPurgingPendingKillObjects = GetTimeBetweenGarbageCollectionPasses(bHasPlayersConnected);
// See if we should delay garbage collect for this frame
if (bShouldDelayGarbageCollect)
{
bShouldDelayGarbageCollect = false;
}
else if (IsIncrementalReachabilityAnalysisPending())
{
SCOPE_CYCLE_COUNTER(STAT_GCMarkTime);
PerformIncrementalReachabilityAnalysis(GetReachabilityAnalysisTimeLimit());
}
// Perform incremental purge update if it's pending or in progress.
else if (!IsIncrementalPurgePending()
// Purge reference to pending kill objects every now and so often.
&& (TimeSinceLastPendingKillPurge > TimeBetweenPurgingPendingKillObjects) && TimeBetweenPurgingPendingKillObjects > 0.f)
{
SCOPE_CYCLE_COUNTER(STAT_GCMarkTime);
PerformGarbageCollectionAndCleanupActors();
}
else
{
SCOPE_CYCLE_COUNTER(STAT_GCSweepTime);
float IncGCTime = GetIncrementalGCTimePerFrame();
IncrementalPurgeGarbage(true, IncGCTime);
}
}
}
}
-
PerformIncrementalReachabilityAnalysis() 负责执行增量垃圾回收的分步可达性分析 -
PerformGarbageCollectionAndCleanupActors() 负责执行全量垃圾回收,内容与上文中介绍的全量垃圾回收的逻辑类似,其也使用了 UEngine::TryCollectGarbage() 和 UWorld::CleanupActors 来回收对象。下文会具体介绍 -
IncrementalPurgeGarbage() 负责销毁 UObject 和释放内存,这时可达性分析已经完成,后面的章节会具体介绍
if (const int32 Interval = CVarCollectGarbageEveryFrame.GetValueOnGameThread())
{
if (0 == (GFrameCounter % Interval))
{
ForceGarbageCollection(true);
}
}
-
CVarCollectGarbageEveryFrame 获取帧间隔
else if (CVarContinuousIncrementalGC.GetValueOnGameThread() > 0 &&
!IsIncrementalReachabilityAnalysisPending() &&
!IsIncrementalUnhashPending() &&
!IsIncrementalPurgePending())
{
ForceGarbageCollection(false);
}
LastGCFrame = GFrameCounter;
}
-
CVarContinuousIncrementalGC 启用持续增量 GC todo
else if (IsIncrementalReachabilityAnalysisPending())
{
PerformIncrementalReachabilityAnalysis(GetReachabilityAnalysisTimeLimit());
}
}
PerformGarbageCollectionAndCleanupActors()
void UEngine::PerformGarbageCollectionAndCleanupActors()
{
// We don't collect garbage while there are outstanding async load requests as we would need
// to block on loading the remaining data.
if (GPerformGCWhileAsyncLoading || !IsAsyncLoading())
{
bool bForcePurge = true;
for (FWorldContext& Context : WorldList)
{
UWorld* World = Context.World();
if (World != nullptr && World->IsGameWorld())
{
bForcePurge = false;
break;
}
}
-
GPerformGCWhileAsyncLoading 表示是否允许在异步加载时执行 GC,其配合 IsAsyncLoading() 进行异步加载时是否触发 GC 的判断 -
bForcePurge 默认为 true,但是一旦发现存在活跃的游戏世界( World != nullptr && World->IsGameWorld )那么就取消强制清理。这样有助于减少游戏卡顿,优先保证流畅性。
// Perform housekeeping.
if (TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, bForcePurge))
{
ForEachObjectOfClass(UWorld::StaticClass(), [](UObject* World)
{
CastChecked<UWorld>(World)->CleanupActors();
});
// Reset counter.
TimeSinceLastPendingKillPurge = 0.0f;
bFullPurgeTriggered = false;
LastGCFrame = GFrameCounter;
}
}
}
垃圾回收的执行过程
// if(bFullPurgeTriggered)
if (TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true))
{
ForEachObjectOfClass(UWorld::StaticClass(),[](UObject* World)
{
CastChecked<UWorld>(World)->CleanupActors();
});
TimeSinceLastPendingKillPurge = 0.0f;
bFullPurgeTriggered = false;
bShouldDelayGarbageCollect = false;
}
// UEngine::PerformGarbageCollectionAndCleanupActors
if (TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, bForcePurge))
{
ForEachObjectOfClass(UWorld::StaticClass(), [](UObject* World)
{
CastChecked<UWorld>(World)->CleanupActors();
});
TimeSinceLastPendingKillPurge = 0.0f;
bFullPurgeTriggered = false;
LastGCFrame = GFrameCounter;
}
回收过程:
TryCollectGarbage
-
进行预回收 UE::GC::PreCollectGarbageImpl<true>(ObjectKeepFlags); -
进行可达性分析 PerformReachabilityAnalysis() ; -
进行后回收 UE::GC::PostCollectGarbageImpl<true>(ObjectKeepFlags);
可达性分析
// GarbageColleciton.cpp
void PerformReachabilityAnalysis(EObjectFlags KeepFlags, const EGCOptions Options) {
LLM_SCOPE(ELLMTag::GC);
const bool bIsGarbageTracking = !GReachabilityState.IsSuspended() && Stats.bFoundGarbageRef;
if (!GReachabilityState.IsSuspended()) {
StartReachabilityAnalysis(KeepFlags, Options);
// We start verse GC here so that the objects are unmarked prior to verse marking them
StartVerseGC();
}
{
const double StartTime = FPlatformTime::Seconds();
while (true) {
PerformReachabilityAnalysisPass(Options);
if (GReachabilityState.IsSuspended()) {
// We may have suspended either via incremental timeout, or because verse GC is still marking.
// If we are not incremental, keep going while verse GC adds to GReachableObjects.
if (EnumHasAnyFlags(Options, EGCOptions::IncrementalReachability)) {
break;
}
}
else if (Private::GReachableObjects.IsEmpty() && Private::GReachableClusters.IsEmpty()) {
// We terminate verse GC here now that both sides have nothing left to mark.
// This check must happen only when !IsSuspended, so verse GC can no longer add to GReachableObjects.
StopVerseGC();
break;
}
}
// ... time tracing
}
-
增量模式挂起,一般是执行时间超过一帧的时间预算,这时会等到下一帧处理 -
可达对象或者可达对象簇为空,意味着数据处理完毕
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// Allowing external systems to add object roots. This can't be done through AddReferencedObjects
// because it may require tracing objects (via FGarbageCollectionTracer) multiple times
if (!GReachabilityState.IsSuspended())
{
const double StartTime = FPlatformTime::Seconds();
FCoreUObjectDelegates::TraceExternalRootsForReachabilityAnalysis.Broadcast(*this, KeepFlags, !(Options & EGCOptions::Parallel));
GGCStats.TraceExternalRootsTime += FPlatformTime::Seconds() - StartTime;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
PerformReachabilityAnalysisPass
// GarbageCollection.cpp
void PerformReachabilityAnalysisPass(const EGCOptions Options)
{
FContextPoolScope Pool;
FWorkerContext* Context = nullptr;
if (!GReachabilityState.IsSuspended())
{
Context = Pool.AllocateFromPool();
}
else
{
Context = GReachabilityState.GetContextArray()[0];
Context->bDidWork = false;
InitialObjects.Reset();
}
if (!Private::GReachableObjects.IsEmpty())
{
// Add objects marked with the GC barrier to the inital set of objects for the next iteration of incremental reachability
Private::GReachableObjects.PopAllAndEmpty(InitialObjects);
GGCStats.NumBarrierObjects += InitialObjects.Num();
UE_LOG(LogGarbage, Verbose, TEXT("Adding %d object(s) marker by GC barrier to the list of objects to process"), InitialObjects.Num());
ConditionallyAddBarrierReferencesToHistory(*Context);
}
else if (GReachabilityState.GetNumIterations() == 0 || (Stats.bFoundGarbageRef && !GReachabilityState.IsSuspended()))
{
Context->InitialNativeReferences = GetInitialReferences(Options);
}
if (!Private::GReachableClusters.IsEmpty())
{
// Process cluster roots that were marked as reachable by the GC barrier
TArray<FUObjectItem*> KeepClusterRefs;
Private::GReachableClusters.PopAllAndEmpty(KeepClusterRefs);
for (FUObjectItem* ObjectItem : KeepClusterRefs)
{
// Mark referenced clusters and mutable objects as reachable
MarkReferencedClustersAsReachable<EGCOptions::None>(ObjectItem->GetClusterIndex(), InitialObjects);
}
}
Context->SetInitialObjectsUnpadded(InitialObjects);
PerformReachabilityAnalysisOnObjects(Context, Options);
if (!GReachabilityState.IsSuspended())
{
GReachabilityState.ResetWorkers();
Stats.AddStats(Context->Stats);
GReachabilityState.UpdateStats(Context->Stats);
Pool.ReturnToPool(Context);
}
}
-
重置工作线程 -
合并统计信息 -
归还上下文到对象池
清除垃圾
template<bool bPerformFullPurge>
void PostCollectGarbageImpl(EObjectFlags KeepFlags)
{
const double PostCollectStartTime = FPlatformTime::Seconds();
using namespace UE::GC;
using namespace UE::GC::Private;
if (!GIsIncrementalReachabilityPending)
{
FContextPoolScope ContextPool;
TConstArrayView<TUniquePtr<FWorkerContext>> AllContexts = ContextPool.PeekFree();
// This needs to happen before clusters get dissolved otherwisise cluster information will be missing from history
UpdateGCHistory(AllContexts);
// 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);
}
DumpGarbageReferencers(AllContexts);
const EGatherOptions GatherOptions = GetObjectGatherOptions();
DissolveUnreachableClusters(GatherOptions);
// This needs to happen after DissolveUnreachableClusters since it can mark more objects as unreachable
if (GReachabilityState.GetNumIterations() > 1){
ClearWeakReferences<true>(AllContexts);
} else {
ClearWeakReferences<false>(AllContexts);
}
if (bPerformFullPurge) {
ContextPool.Cleanup();
}
GGatherUnreachableObjectsState.Init();
if (bPerformFullPurge || !GAllowIncrementalGather || !FGCFlags::IsIncrementalGatherUnreachableSupported()) {
GatherUnreachableObjects(GatherOptions, /*TimeLimit =*/ 0.0);
}
}
GIsGarbageCollectingAndLockingUObjectHashTables = false;
UnlockUObjectHashTables();
GIsGarbageCollecting = false;
// The hash tables lock was released when reachability analysis was done.
// BeginDestroy, FinishDestroy, destructors and callbacks are allowed to call functions like StaticAllocateObject and StaticFindObject.
// Now release the GC lock to allow async loading and other threads to perform UObject operations under the FGCScopeGuard.
ReleaseGCLock();
if (!GIsIncrementalReachabilityPending)
{
...
// Perform a full purge by not using a time limit for the incremental purge.
if (bPerformFullPurge)
{
IncrementalPurgeGarbage(false);
}
...
}
//... log and trace
}
-
IncrementalPurgeGarbage() 函数是回收内存的核心函数,在 PostCollectGarbageImpl 中只会进行全量回收。但是这个函数也会在 UEngine::ConditionalCollectGarbage() 中被调用
GatherUnreachableObjects
while (Iterator.Index <= Iterator.LastIndex)
{
FUObjectItem* ObjectItem = &GUObjectArray.GetObjectItemArrayUnsafe()[Iterator.Index++];
if (FGCFlags::IsMaybeUnreachable_ForGC(ObjectItem))
{
checkf(!ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot), TEXT("Unreachable cluster root found. Unreachable clusters should have been dissolved in DissolveUnreachableClusters!"));
FGCFlags::SetUnreachable(ObjectItem);
Iterator.Payload.Add({ ObjectItem });
}
if (Timer.IsTimeLimitExceeded())
{
return;
}
}
-
FGCFlags::IsMaybeUnreachable_ForGC 检查对象是否有 MaybeUnreachableObjectFlag -
Iterator.Payload.Add({ ObjectItem }) 将不可达对象添加到一个临时集合,在后续使用
IncrementalDestroyGarbage
if (IsIncrementalUnhashPending())
{
bTimeLimitReached = UnhashUnreachableObjects(bUseTimeLimit, TimeLimit);
if (GUnrechableObjectIndex >= GUnreachableObjects.Num())
{
FScopedCBDProfile::DumpProfile();
}
}
if (!bTimeLimitReached)
{
bCompleted = IncrementalDestroyGarbage(bUseTimeLimit, TimeLimit);
}
-
将不可达对象从全局哈希表删除。全局哈希表用于快速定位对象如 StaticFindObject ;也用于按路径索引资源 -
执行所有不可达对象的 BeginDestroy() 函数 -
返回一个是否超时的布尔值
bool IncrementalDestroyGarbage(bool bUseTimeLimit, double TimeLimit)
{
...
while (GObjCurrentPurgeObjectIndex < GUnreachableObjects.Num())
{
FUObjectItem* ObjectItem = GUnreachableObjects[GObjCurrentPurgeObjectIndex].ObjectItem;
checkSlow(ObjectItem);
check(!FGCFlags::IsReachable_ForGC(ObjectItem) && FGCFlags::IsMaybeUnreachable_ForGC(ObjectItem));
if (ObjectItem->IsUnreachable())
{
UObject* Object = static_cast<UObject*>(ObjectItem->Object);
// Object should always have had BeginDestroy called on it and never already be destroyed
check( Object->HasAnyFlags( RF_BeginDestroyed ) && !Object->HasAnyFlags( RF_FinishDestroyed ) );
// Only proceed with destroying the object if the asynchronous cleanup started by BeginDestroy has finished.
if(Object->IsReadyForFinishDestroy())
{
UE::GC::GDetailedStats.IncPurgeCount(Object);
// Send FinishDestroy message.
Object->ConditionalFinishDestroy();
}
else
{
GGCObjectsPendingDestruction.Add(Object);
GGCObjectsPendingDestructionCount++;
}
}
-
Destruction(下文写作“毁灭”):根据源码可以观察到,Destruction 在这里的语义指调用 FinishDestroy() ,不包含释放内存 -
IsReadyForFinishDestroy() 是一个 UObject 的公共虚函数, UObject 的默认实现是返回 true ,通常被覆写,例如 AActor 中会在渲染结束时才为 true -
ConditionalFinishDestroy() 会执行 FinishDestroy() 函数 -
GGCObjectsPendingDestruction 缓存了待销毁的对象的指针,这个变量是全局的,如果当帧没有处理完里面的内容(如超过了时限),会在下一帧处理。下文将这个对象称为“待销毁对象数组”
++GObjCurrentPurgeObjectIndex;
// Only check time limit every so often to avoid calling FPlatformTime::Seconds too often.
const bool bPollTimeLimit = ((TimeLimitTimePollCounter++) % TimeLimitEnforcementGranularityForDestroy == 0);
if( bUseTimeLimit && bPollTimeLimit && ((FPlatformTime::Seconds() - GCStartTime) > TimeLimit) )
{
bTimeLimitReached = true;
break;
}
}
}
// Have we finished the first round of attempting to call FinishDestroy on unreachable objects?
if (GObjCurrentPurgeObjectIndex >= GUnreachableObjects.Num())
{
...
while( GGCObjectsPendingDestructionCount > 0 )
{
int32 CurPendingObjIndex = 0;
while( CurPendingObjIndex < GGCObjectsPendingDestructionCount )
{
...
}
if( bUseTimeLimit )
{
break;
}
else if( GGCObjectsPendingDestructionCount > 0 ) {
if (FPlatformProperties::RequiresCookedData()) {...}
// Sleep before the next pass to give the render thread some time to release fences.
FPlatformProcess::Sleep( 0 );
}
LastLoopObjectsPendingDestructionCount = GGCObjectsPendingDestructionCount;
}
// Have all objects been destroyed now?
if( GGCObjectsPendingDestructionCount == 0 )
{
...
// Release memory we used for objects pending destruction, leaving some slack space
GGCObjectsPendingDestruction.Empty( 256 );
// Destroy has been routed to all objects so it's safe to delete objects now.
GObjFinishDestroyHasBeenRoutedToAllObjects = true;
GObjCurrentPurgeObjectIndexNeedsReset = true;
GWarningTimeOutHasBeenDisplayedGC = false;
}
}
}
if (GObjFinishDestroyHasBeenRoutedToAllObjects && !bTimeLimitReached)
{
...
GUObjectPurge.DestroyObjects(bUseTimeLimit, TimeLimit, GCStartTime);
...
if (GUObjectPurge.IsFinished())
{
bCompleted = true;
...
}
}
//...log
return bCompleted;
}
UWorld::CleanupActors()
-
触发 OnDestroy 事件和蓝图逻辑 -
标记 Actor 为待销毁(包括了将其从 ULevel 的 Actor 数组中设置为 nullptr 等, MarkAsGarbage() 等) -
从游戏逻辑中移除,不参与渲染、物理计算
void UWorld::CleanupActors()
{
// 遍历所有世界
for (ULevel* Level : Levels)
{
if(ensure(Level != nullptr) && (CurrentLevelPendingVisibility != Level))
{
const int32 FirstDynamicIndex = 2;
int32 NumActorsToRemove = 0;
// 反向遍历 Actors,因为会发生删除 Actor
for(int32 ActorIndex=Level->Actors.Num()-1;
ActorIndex>=FirstDynamicIndex;
ActorIndex-- )
{
// 为了减少内存操作次数,在遇到空 Actor 时,
// 引擎会增加一个连续空 Actor 计数,即上面的 NumActorsToRemove
// 在遇到非空对象的时候,才会把之前遇到的一系列连续的空 Actor 删除
if (Level->Actors[ActorIndex] == nullptr)
{
++NumActorsToRemove;
}
else if (NumActorsToRemove > 0)
{
Level->Actors.RemoveAt(ActorIndex+1, NumActorsToRemove, EAllowShrinking::No);
NumActorsToRemove = 0;
}
}
if (NumActorsToRemove > 0)
{
Level->Actors.RemoveAt(FirstDynamicIndex, NumActorsToRemove, EAllowShrinking::No);
}
}
}
}
引用与对象图
-
自动收集 UPROPERTY 引用,这部分主要配合反射系统完成 -
对于非 UPROPERTY 引用,可以通过 UGCObject 和重写 UObject::AddReferencedObjects 方法来添加自定义引用添加行为(该函数默认在运行时没有行为)
自动引用生成与遍历
typedef void(FRealtimeGC::*ReachabilityAnalysisFn)(FWorkerContext&);
/** Pointers to functions used for Reachability Analysis */
ReachabilityAnalysisFn ReachabilityAnalysisFunctions[8];
FRealtimeGC()
{
ReachabilityAnalysisFunctions[GetGCFunctionIndex(EGCOptions::None)] = &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal<EGCOptions::None | EGCOptions::None>;
...
}
template <EGCOptions Options>
void PerformReachabilityAnalysisOnObjectsInternal(FWorkerContext& Context)
{
TRACE_CPUPROFILER_EVENT_SCOPE(PerformReachabilityAnalysisOnObjectsInternal);
//... Editor Only
TReachabilityProcessor<Options> Processor;
CollectReferencesForGC<TReachabilityCollector<Options>>(Processor, Context);
}
-
TReachabilityProcessor 是实现可达性分析的核心逻辑的类,下文中我们会具体介绍。
template<class CollectorType, class ProcessorType>
FORCEINLINE void CollectReferencesForGC(ProcessorType& Processor, UE::GC::FWorkerContext& Context)
{
using FastReferenceCollector = TFastReferenceCollector<ProcessorType, CollectorType>;
if constexpr (IsParallel(ProcessorType::Options))
{ ProcessAsync([](void* P, FWorkerContext& C) { FastReferenceCollector(*reinterpret_cast<ProcessorType*>(P)).ProcessObjectArray(C); }, &Processor, Context);
} else
{
if (!GReachabilityState.IsSuspended())
{ GReachabilityState.SetupWorkers(1);
GReachabilityState.GetContextArray()[0] = &Context;
}
FastReferenceCollector(Processor).ProcessObjectArray(Context);
Context.ResetInitialObjects();
Context.InitialNativeReferences = TConstArrayView<UObject**>();
GReachabilityState.CheckIfAnyContextIsSuspended();
}}
FastReferenceCollector(Processor).ProcessObjectArray(Context);
void ProcessObjectArray(FWorkerContext& Context)
{
Context.bDidWork = true;
Context.bIsSuspended = false;
static_assert(!EnumHasAllFlags(Options, EGCOptions::Parallel | EGCOptions::AutogenerateSchemas), "Can't assemble token streams in parallel");
CollectorType Collector(Processor, Context);
// Either TDirectDispatcher living on the stack or TBatchDispatcher reference owned by Collector
decltype(GetDispatcher(Collector, Processor, Context)) Dispatcher = GetDispatcher(Collector, Processor, Context);
-
收集器( Collector )负责收集引用 -
分发器( Dispatcher )根据(单/多线程)选择任务分发策略,对当前工作物体们进行一次引用遍历步进(Process)得到的引用首先会存储在分发器中,接着会通过块(Block)的方式分发给各个线程具体,下文会详细讲述这个过程
StoleContext:
// Process initial references first
Context.ReferencingObject = FGCObject::GGCObjectReferencer;
for (UObject** InitialReference : Context.InitialNativeReferences)
{
Dispatcher.HandleKillableReference(*InitialReference, EMemberlessId::InitialReference, EOrigin::Other);
}
TConstArrayView<UObject*> CurrentObjects = Context.InitialObjects;
-
FGCObject::GGCObjectReferencer 是全局的根引对象引用管理器
while (true)
{
Context.Stats.AddObjects(CurrentObjects.Num());
ProcessObjects(Dispatcher, CurrentObjects);
-
CurrentObjects 是一个 UObject 指针的数组,存储着当前的待处理的对象 -
ProcessObjects 就是遍历对象的引用的地方,它会将遍历到引用暂时存储在分发器 Dispatcher 中,本文会在之后会具体介绍这个函数,在此可以暂时认为我们已经得到了当前对象的引用关系在分发器中
// Free finished work block
if (CurrentObjects.GetData() != Context.InitialObjects.GetData())
{
Context.ObjectsToSerialize.FreeOwningBlock(CurrentObjects.GetData());
}
if (Processor.IsTimeLimitExceeded())
{
FlushWork(Dispatcher);
Dispatcher.Suspend();
SuspendWork(Context);
return;
}
-
Processor.IsTimeLimitExceeded() 检查是否超过增量式回收的每帧预算 -
FlushWork 将 Dispatcher 中的工作转移出来,暂存到 TFastReferenceCollector 中
-
FWorkBlock 是一组固定数量的对象的集合。分块目的是将可达性分析的任务分割成多个快,有助于并行优化和增量式回收;同时 FWorkBlock 被设计成缓存友好的数据结构,有很好的性能表现。
int32 BlockSize = FWorkBlock::ObjectCapacity;
FWorkBlockifier& RemainingObjects = Context.ObjectsToSerialize;
FWorkBlock* Block = RemainingObjects.PopFullBlock<Options>();
if (!Block)
{
if constexpr (bIsParallel)
{
FSlowARO::ProcessUnbalancedCalls(Context, Collector);
}
StoleARO:
FlushWork(Dispatcher);
if (Block = RemainingObjects.PopFullBlock<Options>(); Block);
else if (Block = RemainingObjects.PopPartialBlock(/* out if successful */ BlockSize); Block);
-
BlockSize 在上面的代码中出现过,是一个工作块的容量 -
RemainingObjects.PopPartialBlock 必须是原子性的,因为有多个线程可能会请求进行此操作
else if (bIsParallel) // if constexpr yields MSVC unreferenced label warning
{
switch (StealWork(/* in-out */ Context, Collector, /* out */ Block, Options))
{
case ELoot::Nothing: break; // Done, stop working
case ELoot::Block: break; // Stole full block, process it
case ELoot::ARO: goto StoleARO; // Stole and made ARO calls that feed into Dispatcher queues and RemainingObjects
case ELoot::Context: goto StoleContext; // Stole initial references and initial objects worker that hasn't started working
}
}
-
Nothing :什么都不干等到下面执行结束 -
Block :上文中已经判断过了(实际上这个线程也不会执行到这里),直接获取一个完整的块,在下一次循环工作 -
ARO (AddReferencedObjects) :窃取到其它线程未处理的手动引用工作,此时 Block 已经被替换成其它线程未处理的工作块, goto 到上面的 StoleARO 直接工作即可 -
Context :窃取到其它线程未完成的上下文任务,此时上下问 Context 变量已经被替换成了其它上下文的工作, goto 到上面的 StoleContext 直接工作即可
if (!Block)
{
break;
}
}
CurrentObjects = MakeArrayView(Block->Objects, BlockSize);
} // while (true)
Processor.LogDetailedStatsSummary();
}
FORCEINLINE_DEBUGGABLE void ProcessObjects(DispatcherType& Dispatcher, TConstArrayView<UObject*> CurrentObjects)
{
for (FPrefetchingObjectIterator It(CurrentObjects); It.HasMore(); It.Advance())
{
UObject* CurrentObject = It.GetCurrentObject();
UClass* Class = CurrentObject->GetClass();
UObject* Outer = CurrentObject->GetOuter();
-
UClass 是反射类,记录了一个类的反射信息,在下文中获取类的引用关系时会用到。 UClass 的元数据在编译时生成,运行时只读 -
Outer 变量是当前对象所在的父容器,在 UE 中,对象的父容器只存在一个,这也意味着一个对象有一条唯一的引用路径
if (!!(Options & EGCOptions::AutogenerateSchemas) && !Class->HasAnyClassFlags(CLASS_TokenStreamAssembled))
{
Class->AssembleReferenceTokenStream();
}
FSchemaView Schema = Class->ReferenceSchema.Get();
Dispatcher.Context.ReferencingObject = CurrentObject;
-
FSchemaView 类描述了一个类的所有强引用关系
// Emit base references
Dispatcher.HandleImmutableReference(Class, EMemberlessId::Class, EOrigin::Other);
Dispatcher.HandleImmutableReference(Outer, EMemberlessId::Outer, EOrigin::Other);
#if WITH_EDITOR
UObject* Package = CurrentObject->GetExternalPackageInternal();
Package = Package != CurrentObject ? Package : nullptr;
Dispatcher.HandleImmutableReference(Package, EMemberlessId::ExternalPackage, EOrigin::Other);
#endif
-
HandleImmutableReference 会在内部调用 HandleReferenceDirectly ,它会将物体指针推到 ImmutableBatcher 中,确保不会被回收
if (!Schema.IsEmpty())
{
typename DispatcherType::SchemaStackScopeType SchemaStack(Dispatcher.Context, Schema);
Processor.BeginTimingObject(CurrentObject);
Private::VisitMembers(Dispatcher, Schema, CurrentObject);
Processor.UpdateDetailedStats(CurrentObject);
}}}
-
BeginTimingObject 和 UpdateDetailedStats 用于计时和性能分析 -
VisitMembers 执行遍历类图的地方,它会将物体收集到 Dispatcher 的 KillableBatcher 和 ImmutableBatcher 中