源码版本: 4.22.3
一:自动GC部分
void UWorld::Tick( ELevelTick TickType, float DeltaSeconds )
{
//略
GEngine->ConditionalCollectGarbage();
//略
}
下面将conditionCollectGarbage 方法自己的理解通过注释的方式记录。
void UEngine::ConditionalCollectGarbage()
{
//防止同一帧多次进入
if (GFrameCounter != LastGCFrame)
{
//略
//如果是全量GC
if (bFullPurgeTriggered)
{
//全量GC
if (TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true))
{
//遍历level cleanup nullptr
ForEachObjectOfClass(UWorld::StaticClass(),[](UObject* World)
{
CastChecked<UWorld>(World)->CleanupActors();
});
//reset flag
bFullPurgeTriggered = false;
bShouldDelayGarbageCollect = false;
TimeSinceLastPendingKillPurge = 0.0f;
}
}
//增量GC
else
{
bool bHasAWorldBegunPlay = false;
ForEachObjectOfClass(UWorld::StaticClass(), [&bHasAWorldBegunPlay](UObject* World)
{
bHasAWorldBegunPlay = bHasAWorldBegunPlay || CastChecked<UWorld>(World)->HasBegunPlay();
});
//如果 world beginplay
if (bHasAWorldBegunPlay)
{
TimeSinceLastPendingKillPurge += FApp::GetDeltaTime();
//自动GC间隔时间,可配置gc.TimeBetweenPurgingPendingKillObjects
const float TimeBetweenPurgingPendingKillObjects =
//并且会根据内存大小等情况动态调整,默认60s
GetTimeBetweenGarbageCollectionPasses();
// See if we should delay garbage collect for this frame
if (bShouldDelayGarbageCollect)
{
//某些情况下会跳过
bShouldDelayGarbageCollect = false;
}
// 如果没有正在进行的回收
else if (!IsIncrementalPurgePending()
//并且 从上次回收已经超过了自动gc间隔
&& (TimeSinceLastPendingKillPurge > TimeBetweenPurgingPendingKillObjects) && TimeBetweenPurgingPendingKillObjects > 0.f)
{
//增量得进行一次标记 清扫
SCOPE_CYCLE_COUNTER(STAT_GCMarkTime);
PerformGarbageCollectionAndCleanupActors();
}
else
{
//继续增量回收未完成得
SCOPE_CYCLE_COUNTER(STAT_GCSweepTime);
IncrementalPurgeGarbage(true);
}
}
}
//如果gc.CollectGarbageEveryFrame配置了>0, 则每帧都执行全量GC(用于调试)
if (CVarCollectGarbageEveryFrame.GetValueOnGameThread() > 0)
{
ForceGarbageCollection(true);
}
LastGCFrame = GFrameCounter;
}
}
bool TryCollectGarbage(EObjectFlags KeepFlags, bool bPerformFullPurge)
{
// 尝试获取gc锁
bool bCanRunGC = FGCCSyncObject::Get().TryGCLock();
if (!bCanRunGC)
{
//gc.NumRetriesBeforeForcingGC 默认10可配置 跳过gc次数
if (GNumRetriesBeforeForcingGC > 0 && GNumAttemptsSinceLastGC > GNumRetriesBeforeForcingGC)
{
// Force GC and block main thread
UE_LOG(LogGarbage, Warning, TEXT("TryCollectGarbage: forcing GC after %d skipped attempts."), GNumAttemptsSinceLastGC);
//强制gc reset flag
GNumAttemptsSinceLastGC = 0;
AcquireGCLock();
bCanRunGC = true;
}
}
if (bCanRunGC)
{
// Perform actual garbage collection
//垃圾回收 bPerformFullPurge:是否进行一次全量的回收
CollectGarbageInternal(KeepFlags, bPerformFullPurge);
// Other threads are free to use UObjects
ReleaseGCLock();
}
else
{
GNumAttemptsSinceLastGC++;
}
return bCanRunGC;
}
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 (!IsAsyncLoading())
{
// Perform housekeeping. 增量
if (TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, false))
{
ForEachObjectOfClass(UWorld::StaticClass(), [](UObject* World)
{
CastChecked<UWorld>(World)->CleanupActors();
});
// Reset counter.
TimeSinceLastPendingKillPurge = 0.0f;
bFullPurgeTriggered = false;
LastGCFrame = GFrameCounter;
}
}
}
另外的:
void UEngine::ForceGarbageCollection(bool bForcePurge/*=false*/)
{
TimeSinceLastPendingKillPurge = 1.0f + GetTimeBetweenGarbageCollectionPasses();
bFullPurgeTriggered = bFullPurgeTriggered || bForcePurge;
}
通过这个方法,显示在下一帧触发一次自动gc(并不一定,只是try)。
二:手动GC部分 todo
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)
{
// Reset GC skip counter
GNumAttemptsSinceLastGC = 0;
// Flush streaming before GC if requested
//gc.FlushStreamingOnGC 配置 是否flush异步加载流
if (GFlushStreamingOnGC)
{
if (IsAsyncLoading())
{
//log
}
FGCCSyncObject::Get().GCUnlock();
FlushAsyncLoading();
FGCCSyncObject::Get().GCLock();
}
//pre delegate broadcast
FCoreUObjectDelegates::GetPreGarbageCollectDelegate().Broadcast();
GLastGCFrame = GFrameCounter;
{
FGCScopeLock GCLock;
//如果需要一次完全gc或者之前的增量gc未完成,全量gc一次
if (GObjIncrementalPurgeIsInProgress || GObjPurgeIsRequired)
{
IncrementalPurgeGarbage(false);
FMemory::Trim();
}
//略
//判断是否强制单线程gc
const bool bForceSingleThreadedGC = !FApp::ShouldUseThreadingForPerformance() || !FPlatformProcess::SupportsMultithreading() ||
#if PLATFORM_SUPPORTS_MULTITHREADED_GC
(FPlatformMisc::NumberOfCores() < 2 || GAllowParallelGC == 0 || PERF_DETAILED_PER_CLASS_GC_STATS);
#else //PLATFORM_SUPPORTS_MULTITHREADED_GC
true;
#endif //PLATFORM_SUPPORTS_MULTITHREADED_GC
// Perform reachability analysis.
//可达性分析
{
const double StartTime = FPlatformTime::Seconds();
FRealtimeGC TagUsedRealtimeGC;
TagUsedRealtimeGC.PerformReachabilityAnalysis(KeepFlags, bForceSingleThreadedGC);
}
// Reconstruct clusters if needed
//回收一些cluster?
if (GUObjectClusters.ClustersNeedDissolving())
{
const double StartTime = FPlatformTime::Seconds();
GUObjectClusters.DissolveClusters();
}
// Fire post-reachability analysis hooks
FCoreUObjectDelegates::PostReachabilityAnalysis.Broadcast();
{
//清除弱引用 (增量或全量)
FGCArrayPool::Get().ClearWeakReferences(bPerformFullPurge);
//收集 达不到的object
GatherUnreachableObjects(bForceSingleThreadedGC);
if (bPerformFullPurge || !GIncrementalBeginDestroyEnabled)
{
UnhashUnreachableObjects(/**bUseTimeLimit = */ false);
FScopedCBDProfile::DumpProfile();
}
}
// Set flag to indicate that we are relying on a purge to be performed.
GObjPurgeIsRequired = true;
// Reset purged count.
GPurgedObjectCountSinceLastMarkPhase = 0;
GObjCurrentPurgeObjectIndexResetPastPermanent = true;
// Perform a full purge by not using a time limit for the incremental purge. The Editor always does a full purge.
//编辑器 使用全量回收
if (bPerformFullPurge || GIsEditor)
{
IncrementalPurgeGarbage(false);
}
//压缩 uobject 的哈希表
if (bPerformFullPurge)
{
ShrinkUObjectHashTables();
}
// Destroy all pending delete linkers
DeleteLoaders();
// Trim allocator memory
FMemory::Trim();
}
// Route callbacks to verify GC assumptions
FCoreUObjectDelegates::GetPostGarbageCollect().Broadcast();
STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, TEXT( "GarbageCollection - End" ) );
}
void IncrementalPurgeGarbage( bool bUseTimeLimit, float TimeLimit )
{
//Shut down the object manager
if (GExitPurge)
{
GObjPurgeIsRequired = true;
GUObjectArray.DisableDisregardForGC();
GObjCurrentPurgeObjectIndexNeedsReset = true;
GObjCurrentPurgeObjectIndexResetPastPermanent = false;
}
// Early out if there is nothing to do.
if( !GObjPurgeIsRequired )
{
return;
}
bool bCompleted = false;
//設置flag 利用构造和析构 在多线程环境下
struct FResetPurgeProgress
{
bool& bCompletedRef;
FResetPurgeProgress(bool& bInCompletedRef)
: bCompletedRef(bInCompletedRef)
{
// Incremental purge is now in progress.
GObjIncrementalPurgeIsInProgress = true;
FPlatformMisc::MemoryBarrier();
}
~FResetPurgeProgress()
{
if (bCompletedRef)
{
GObjIncrementalPurgeIsInProgress = false;
FPlatformMisc::MemoryBarrier();
}
}
} ResetPurgeProgress(bCompleted);
// Keep track of start time to enforce time limit unless bForceFullPurge is true;
GCStartTime = FPlatformTime::Seconds();
bool bTimeLimitReached = false;
// Depending on platform FPlatformTime::Seconds might take a noticeable amount of time if called thousands of times so we avoid
// enforcing the time limit too often, especially as neither Destroy nor actual deletion should take significant
// amounts of time.
const int32 TimeLimitEnforcementGranularityForDestroy = 10;
const int32 TimeLimitEnforcementGranularityForDeletion = 100;
if (GUnrechableObjectIndex < GUnreachableObjects.Num())
{
{
FConditionalGCLock ScopedGCLock;
//遍历unreached object begin destroy -> finish destroy (增量或全量)
//调用begin destroy(LowLevelRename(NAME_None) && SetLinker( NULL, INDEX_NONE
//在派生类中可能会处理一些 异步资源等等 ) )
bTimeLimitReached = UnhashUnreachableObjects(bUseTimeLimit, TimeLimit);
}
if (GUnrechableObjectIndex >= GUnreachableObjects.Num())
{
FScopedCBDProfile::DumpProfile();
}
}
FGCScopeLock GCLock;
if( !GObjFinishDestroyHasBeenRoutedToAllObjects && !bTimeLimitReached )
{
check(GUnrechableObjectIndex >= GUnreachableObjects.Num());
int32 TimeLimitTimePollCounter = 0;
int32 FinishDestroyTimePollCounter = 0;
if (GObjCurrentPurgeObjectIndexNeedsReset)
{
// iterators don't have an op=, so we destroy it and reconstruct it with a placement new
// GObjCurrentPurgeObjectIndex = FRawObjectIterator(GObjCurrentPurgeObjectIndexResetPastPermanent);
GObjCurrentPurgeObjectIndex.~FRawObjectIterator();
new (&GObjCurrentPurgeObjectIndex) FRawObjectIterator(GObjCurrentPurgeObjectIndexResetPastPermanent);
GObjCurrentPurgeObjectIndexNeedsReset = false;
}
//当前清除对象的迭代器
while( GObjCurrentPurgeObjectIndex )
{
FUObjectItem* ObjectItem = *GObjCurrentPurgeObjectIndex;
if (ObjectItem->IsUnreachable())
{
UObject* Object = static_cast<UObject*>(ObjectItem->Object);
if(Object->IsReadyForFinishDestroy())
{
Object->ConditionalFinishDestroy();
}
else
{
//没有准备好的object
GGCObjectsPendingDestruction.Add(Object);
GGCObjectsPendingDestructionCount++;
}
}
++GObjCurrentPurgeObjectIndex;
const bool bPollTimeLimit = ((TimeLimitTimePollCounter++) % TimeLimitEnforcementGranularityForDestroy == 0);
if( bUseTimeLimit && bPollTimeLimit && ((FPlatformTime::Seconds() - GCStartTime) > TimeLimit) )
{
bTimeLimitReached = true;
break;
}
}
if( !GObjCurrentPurgeObjectIndex )
{
//defer 遍历的Uobject
int32 LastLoopObjectsPendingDestructionCount = GGCObjectsPendingDestructionCount;
while( GGCObjectsPendingDestructionCount > 0 )
{
int32 CurPendingObjIndex = 0;
while( CurPendingObjIndex < GGCObjectsPendingDestructionCount )
{
UObject* Object = GGCObjectsPendingDestruction[ CurPendingObjIndex ];
if( Object->IsReadyForFinishDestroy() )
{
const FName& ClassName = Object->GetClass()->GetFName();
int32 InstanceCount = GClassToPurgeCountMap.FindRef( ClassName );
GClassToPurgeCountMap.Add( ClassName, ++InstanceCount );
Object->ConditionalFinishDestroy();
{
GGCObjectsPendingDestruction[ CurPendingObjIndex ] = GGCObjectsPendingDestruction[ GGCObjectsPendingDestructionCount - 1 ];
GGCObjectsPendingDestructionCount--;
}
}
else
{
CurPendingObjIndex++;
}
const bool bPollTimeLimit = ((TimeLimitTimePollCounter++) % TimeLimitEnforcementGranularityForDestroy == 0);
if( bUseTimeLimit && bPollTimeLimit && ((FPlatformTime::Seconds() - GCStartTime) > TimeLimit) )
{
bTimeLimitReached = true;
break;
}
}
if( bUseTimeLimit )
{
// A time limit is set and we've completed a full iteration over all leftover objects, so
// go ahead and bail out even if we have more time left or objects left to process. It's
// likely in this case that we're waiting for the render thread.
break;
}
else if( GGCObjectsPendingDestructionCount > 0 )
{
if (FPlatformProperties::RequiresCookedData())
{
const bool bPollTimeLimit = ((FinishDestroyTimePollCounter++) % TimeLimitEnforcementGranularityForDestroy == 0);
#if PLATFORM_IOS
const double MaxTimeForFinishDestroy = 30.0;
#else
const double MaxTimeForFinishDestroy = 10.0;
#endif
// Check if we spent too much time on waiting for FinishDestroy without making any progress
if (LastLoopObjectsPendingDestructionCount == GGCObjectsPendingDestructionCount && bPollTimeLimit &&
((FPlatformTime::Seconds() - GCStartTime) > MaxTimeForFinishDestroy))
{
UE_LOG(LogGarbage, Warning, TEXT("Spent more than %.2fs on routing FinishDestroy to objects (objects in queue: %d)"), MaxTimeForFinishDestroy, GGCObjectsPendingDestructionCount);
UObject* LastObjectNotReadyForFinishDestroy = nullptr;
for (int32 ObjectIndex = 0; ObjectIndex < GGCObjectsPendingDestructionCount; ++ObjectIndex)
{
UObject* Obj = GGCObjectsPendingDestruction[ObjectIndex];
bool bReady = Obj->IsReadyForFinishDestroy();
UE_LOG(LogGarbage, Warning, TEXT(" [%d]: %s, IsReadyForFinishDestroy: %s"),
ObjectIndex,
*GetFullNameSafe(Obj),
bReady ? TEXT("true") : TEXT("false"));
if (!bReady)
{
LastObjectNotReadyForFinishDestroy = Obj;
}
}
#if PLATFORM_DESKTOP
ensureMsgf(0, TEXT("Spent to much time waiting for FinishDestroy for %d object(s) (last object: %s), check log for details"),
GGCObjectsPendingDestructionCount,
*GetFullNameSafe(LastObjectNotReadyForFinishDestroy));
#else
//for non-desktop platforms, make this a warning so that we can die inside of an object member call.
//this will give us a greater chance of getting useful memory inside of the platform minidump.
UE_LOG(LogGarbage, Warning, TEXT("Spent to much time waiting for FinishDestroy for %d object(s) (last object: %s), check log for details"),
GGCObjectsPendingDestructionCount,
*GetFullNameSafe(LastObjectNotReadyForFinishDestroy));
if (LastObjectNotReadyForFinishDestroy)
{
LastObjectNotReadyForFinishDestroy->AbortInsideMemberFunction();
}
else
{
//go through the standard fatal error path if LastObjectNotReadyForFinishDestroy is null.
//this could happen in the current code flow, in the odd case where an object finished readying just in time for the loop above.
UE_LOG(LogGarbage, Fatal, TEXT("LastObjectNotReadyForFinishDestroy is NULL."));
}
#endif
}
}
// 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;
GObjCurrentPurgeObjectIndexResetPastPermanent = !GExitPurge;
}
}
}
if( GObjFinishDestroyHasBeenRoutedToAllObjects && !bTimeLimitReached )
{
// Perform actual object deletion.
// @warning: Can't use FObjectIterator here because classes may be destroyed before objects.
int32 ProcessCount = 0;
if (GObjCurrentPurgeObjectIndexNeedsReset)
{
// iterators don't have an op=, so we destroy it and reconstruct it with a placement new
// GObjCurrentPurgeObjectIndex = FRawObjectIterator(GObjCurrentPurgeObjectIndexResetPastPermanent);
GObjCurrentPurgeObjectIndex.~FRawObjectIterator();
new (&GObjCurrentPurgeObjectIndex) FRawObjectIterator(GObjCurrentPurgeObjectIndexResetPastPermanent);
GObjCurrentPurgeObjectIndexNeedsReset = false;
}
while( GObjCurrentPurgeObjectIndex )
{
//@todo UE4 - A prefetch was removed here. Re-add it. It wasn't right anyway, since it was ten items ahead and the consoles on have 8 prefetch slots
FUObjectItem* ObjectItem = *GObjCurrentPurgeObjectIndex;
checkSlow(ObjectItem);
if (ObjectItem->IsUnreachable())
{
UObject* Object = (UObject*)ObjectItem->Object;
check(Object->HasAllFlags(RF_FinishDestroyed|RF_BeginDestroyed));
GIsPurgingObject = true;
Object->~UObject();
GUObjectAllocator.FreeUObject(Object);
GIsPurgingObject = false;
// Keep track of purged stats.
GPurgedObjectCountSinceLastMarkPhase++;
}
// Advance to the next object.
++GObjCurrentPurgeObjectIndex;
ProcessCount++;
// Only check time limit every so often to avoid calling FPlatformTime::Seconds too often.
if( bUseTimeLimit && (ProcessCount == TimeLimitEnforcementGranularityForDeletion))
{
if ((FPlatformTime::Seconds() - GCStartTime) > TimeLimit)
{
bTimeLimitReached = true;
break;
}
ProcessCount = 0;
}
}
if( !GObjCurrentPurgeObjectIndex )
{
bCompleted = true;
// Incremental purge is finished, time to reset variables.
GObjFinishDestroyHasBeenRoutedToAllObjects = false;
GObjPurgeIsRequired = false;
GObjCurrentPurgeObjectIndexNeedsReset = true;
GObjCurrentPurgeObjectIndexResetPastPermanent = true;
// Log status information.
UE_LOG(LogGarbage, Log, TEXT("GC purged %i objects (%i -> %i)"), GPurgedObjectCountSinceLastMarkPhase, GObjectCountDuringLastMarkPhase.GetValue(), GObjectCountDuringLastMarkPhase.GetValue() - GPurgedObjectCountSinceLastMarkPhase );
#if PERF_DETAILED_PER_CLASS_GC_STATS
LogClassCountInfo( TEXT("objects of"), GClassToPurgeCountMap, 10, GPurgedObjectCountSinceLastMarkPhase );
#endif
}
}
}
//可达性分析
void PerformReachabilityAnalysis(EObjectFlags KeepFlags, bool bForceSingleThreaded = false)
{
/** 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
//非UObject体系的类 归入gc的地方
if (FPlatformProperties::RequiresCookedData() && FGCObject::GGCObjectReferencer && GUObjectArray.IsDisregardForGC(FGCObject::GGCObjectReferencer))
{
ObjectsToSerialize.Add(FGCObject::GGCObjectReferencer);
}
{
//将所有物体标为不可达
MarkObjectsAsUnreachable(ObjectsToSerialize, KeepFlags, bForceSingleThreaded);
}
{
//可达性分析
PerformReachabilityAnalysisOnObjects(ArrayStruct, bForceSingleThreaded);
}
FGCArrayPool::Get().ReturnToPool(ArrayStruct);
}
标记阶段首先将所有物体标为不可达,然后根据引用关系标记。标记时运用了记号流的方式,分析序列化时object拥有的token stream 根据不同的类型找出引用关系标记。 (多线程处理和 cluster优化)
大量粒子等可以使用cluster,加速清扫过程。