浅析UE4垃圾回收
垃圾回收(Garbage Collection)算法分类:
分类一 | 引用计数式 |
通过额外的计数来实时计算对单个对象的引用次数,当引用次数为0时回收对象。 如:微软COM对象、句柄的加减引用值以及C++中的智能指针都是通过引用计数来实现GC的 |
追踪式(UE4) | 达到GC条件时(内存不够用、到达GC间隔时间或者强制GC)通过扫描系统中是否有对象的引用来判断对象是否存活,然后回收无用对象 | |
分类二 | 保守式 |
不能准备识别每一个无用的对象(比如在32位程序中的一个4字节的值,它是不能判断出它是一个对象指针或者是一个数字的),但是能保证在不会错误的回收存活的对象的情况下回收一部分无用对象。 不需要额外的数据来支持查找对象的引用,它将所有的内存数据假定为指针,通过一些条件来判定这个指针是否是一个合法的对象 |
精确式(UE4) | 在回收过程中能准确得识别和回收每一个无用对象的GC方式,为了准确识别每一个对象的引用,通过需要一些额外的数据(比如虚幻中的属性UPROPERTY) | |
分类三 | 搬迁式 | GC过程中需要移动对象在内存中的位置,当然移动对象位置后需要将所有引用到这个对象的地方更新到新位置(有的通过句柄来实现、而有的可能需要修改所有引用内存的指针)。 |
非搬迁式(UE4) | 在GC过程中不需要移动对象的内存位置 | |
分类四 | 实时 | 不需要停止用户执行的GC方式 |
非实时(UE4) | 需要停止用户程序的执行(stop the world) | |
分类五 | 渐进式 | 不会在对象抛弃时立即回收占用的内存资源,而在GC达成一定条件时进行回收操作 |
非渐进式(UE4) | 在对象抛弃时立即回收占用的内存资源 |
UE4采用“追踪式、精确式、非搬迁式、非实时、非渐进式”的标记清扫(Mark-Sweep)GC算法。该算法分为两个阶段:标记阶段(GC Mark)和清扫阶段(GC Sweep) 注:以下代码基于UE 4.25.1版本
UObject对象采用垃圾回收机制,被UPROPERTY宏修饰或在AddReferencedObjects函数被手动添加引用的UObject*成员变量,才能被GC识别和追踪,GC通过这个机制,建立起引用链(Reference Chain)网络。
没有被UPROPERTY宏修饰或在AddReferencedObjects函数被没添加引用的UObject*成员变量无法被虚幻引擎识别,这些对象不会进入引用链网络,不会影响GC系统工作(如:自动清空为nullptr或阻止垃圾回收)。
垃圾回收器定时或某些阶段(如:LoadMap、内存较低等)从根节点Root对象开始搜索,从而追踪所有被引用的对象。
当UObject对象没有直接或间接被根节点Root对象引用或被设置为PendingKill状态,就被GC标记成垃圾,并最终被GC回收。
注1:USTRUCT宏修饰的结构体对象和普通的C++对象一样,是不被GC管理
注2:FGCObject对象和普通的C++对象一样,是不被GC管理
基础概念及操作
置nullptr
若将UObject对象的UPROPERTY宏修饰的UObject*成员变量置成nullptr,只会断掉这个节点的子链路
获取FUObjectItem
/** * Single item in the UObject array. */ 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; }; // 获取UObject对象对应的FUObjectItem FUObjectItem* ObjItem = GUObjectArray.IndexToObject(Obj->GetUniqueID());
Root
1. AddToRoot函数会将UObject对象加到根节点Root上,让其不被GC回收
该UObject对象对应GUObjectArray中的FUObjectItem的Flags会加上EInternalObjectFlags::RootSet标记
2. RemoveFromRoot函数会将UObject对象从根节点Root上移除
会去掉该UObject对象对应GUObjectArray中的FUObjectItem的Flags的EInternalObjectFlags::RootSet标记
标记为PendingKill
1. UObject对象不为Root对象,可通过调用MarkPendingKill函数将把该对象设置为等待回收的对象。
将UObject对象对应GUObjectArray中的FUObjectItem的Flags加上EInternalObjectFlags::PendingKill标记
UObject本身内存数据是没有修改的,可对其成员进行读写
2. 可通过IsPendingKill函数来判断一个UObject是否处于PendingKill状态
3. 调用ClearPendingKill函数来清除PendingKill状态
防止被GC的方法
1. 调用AddToRoot函数将UObject对象加到根节点Root上
LogReferenceChain: (root) MyObject /Engine/Transient.MyObject_0 is not currently reachable.
2. 直接或间接被根节点Root对象引用(UPROPERTY宏修饰的UObject*成员变量 注:UObject*放在UPROPERTY宏修饰的TArray、TMap中也可以)
LogReferenceChain: (root) (standalone) World /Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap.ThirdPersonExampleMap->PersistentLevel LogReferenceChain: Level /Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap.ThirdPersonExampleMap:PersistentLevel::AddReferencedObjects(): PersistentLevel LogReferenceChain: ThirdPersonCharacter_C /Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap.ThirdPersonExampleMap:PersistentLevel.ThirdPersonCharacter_2->m_Obj2 LogReferenceChain: MyObject /Engine/Transient.MyObject_0
3. 直接或间接被存活的FGCObject对象引用(如:static的FGCObject) 注:AddReferencedObject在其上的UObject*对象会被其引用
LogReferenceChain: (root) GCObjectReferencer /Engine/Transient.GCObjectReferencer_0::AddReferencedObjects(): Unknown FGCObject
LogReferenceChain: MyObject /Engine/Transient.MyObject_0
注:以上方法只是防止,并不是指一定不会被GC;通过如下操作,仍然可以让这些UObject对象被GC回收掉
① 对于根节点Root上的UObject要调用RemoveFromRoot函数来去除EInternalObjectFlags::RootSet标记
② 没有EInternalObjectFlags::RootSet标记后,就可调用MarkPendingKill函数将把UObject设置为等待回收的对象
标记阶段(GC Mark)
从根节点集合开始,标记出所有不可达的对象。该阶段执行时需要保证对象引用链不被修改,因此是阻塞的
一个对象一旦被标记为不可达,就被贴上垃圾的标签,不可能再被复活,通过FindObject函数也不能获取该对象,只能等待被GC回收
该阶段后,不会修改UObject对象内存块中任何数据
标记对象为不可达
等待回收UObjec对象,在经过GC Mark时,会将对象设置上EInternalObjectFlags::Unreachable标记,此时调用IsUnreachable函数才会返回true
需要注意的是,在GC Mark之前,即使等待回收UObjec对象已经是不可达的,但是此时由于未设置EInternalObjectFlags::Unreachable标记,因此调用IsUnreachable函数仍然会返回false
可以使用IsPendingKillOrUnreachable函数来判断一个UObject是不是处于PendingKill或Unreachable状态
设置EInternalObjectFlags::Unreachable标记是在TaskGraph线程上做的
此时,游戏线程的Stack如下:
自动更新引用
一个UObject成为等待回收的对象时,会对以下几种情况进行引用更新:
①赋值给其他UObject对象或USTRUCT结构体对象(该对象自身也要加入到引用链网络中)的UPROPERTY宏修饰的UObject*成员变量
②赋值给其他UObject对象(该对象自身也要加入到引用链网络中)的无UPROPERTY宏修饰的UObject*成员变量,但这些成员变量在重写的静态AddReferencedObjects函数中被手动添加引用
// AMyTest1Character重写静态函数AddReferencedObjects // 将无UPROPERTY宏修饰的成员变量m_Obj3手动添加到引用链中 // 该函数在GC Mark和GC Sweep阶段的过程中都会被调用 void AMyTest1Character::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { AMyTest1Character* This = CastChecked<AMyTest1Character>(InThis); Collector.AddReferencedObject(This->m_Obj3); Super::AddReferencedObjects(InThis, Collector); }
③赋值给其他FGCObject对象的无UPROPERTY宏修饰的UObject*成员变量,但这些成员变量在重写的AddReferencedObjects函数中被手动添加引用
// FTestGCObject重写函数AddReferencedObjects // 将无UPROPERTY宏修饰的成员变量m_Obj3手动添加到引用链中 注:非UObject的对象也不允许添加UPROPERTY宏修 // 该函数在GC Mark和GC Sweep阶段的过程中都会被调用 void FTestGCObject::AddReferencedObjects(FReferenceCollector& Collector) // FTestGCObject : public FGCObject { Collector.AddReferencedObject(m_Obj3); // UMyObject* m_Obj3为FTestGCObject的成员变量 }
在GC Mark阶段,会将UObject*成员变量自动清空为nullptr,以防止出现野指针
对于UObject*的TArray成员变量,也会将TArray中对应的UObject*对象清空为nullptr,但不会将该成员从TArray中删除,因此TArray的Num()是不变的
对于UObject*的TMap成员变量,也仅仅是将TMap中对应的key、value的UObject*对象清空为nullptr,不会将该成员从TMap中删除,因此TMap的Num()是不变的。另外,可能会导致TMap中有多个key为nullptr的元素,失去了key的唯一性。
将UObject*成员变量设置成nullptr是在TaskGraph线程上做的
具体置nullptr的代码如下:
此时,游戏线程处于等待状态,其Stack如下:
清扫阶段(GC Sweep)
阶段遍历所有对象,将标记为不可达的对象回收。该阶段可通过限制时间来分帧异步进行,避免导致卡顿
在BeginDestroy函数中将UObject对象的Name设置成空
注1:UObject对象的Flags通过RF_BeginDestroyed标志,来防止BeginDestroy函数执行多次
注2:通过Object->HasAnyFlags(RF_BeginDestroyed)来判断UObject对象是否含有RF_BeginDestroyed标志
在FinishDestroy函数中销毁所有UObject对象的非Native的属性
注1:UObject对象的Flags通过RF_FinishDestroyed标志,来防止FinishDestroy函数执行多次
注2:通过Object->HasAnyFlags(RF_FinishDestroyed)来判断UObject对象是否含有RF_FinishDestroyed标志
最后,在TickDestroyObjects函数中调用UObject的析构函数,并调用GUObjectAllocator.FreeUObject函数来释放内存
判断UObject对象有效性
IsValid全局函数
判断UObject对象指针是否为空以及是否为PendingKill状态
IsValidLowLevel成员函数
依次检查:①UObject对象指针是否为空 ②UObject对象的Class是否为空 ③检查UObject对象的Index是否有效 ④在全局表GUObjectArray中对应的FUObjectItem中对象是否为空,是否与原UObject对象相同
在进行GC Sweep时,在调用UObject的析构函数中,IsValidLowLevel函数仍然能返回true
只有执行GUObjectArray.FreeUObjectIndex函数,发出NotifyUObjectDeleted通知时,IsValidLowLevel函数才返回false
IsValidLowLevelFast成员函数
依次检查:①UObject对象指针是否为空或小于0x100,是否8字节对齐 ②UObject对象的虚表是否为空 ③UObject对象的ObjectFlags是否有效
④UObject对象的Class、Outer是否8字节对齐 ⑤UObject对象的Class及Class的CDO对象是否为空、Class的CDO对象是否8字节对齐
⑥UObject对象的Index是否在全局表GUObjectArray范围内 ⑦UObject对象的Name是否有效
⑧如果参数bool bRecursive为true,还会对UObject对象的Class执行IsValidLowLevelFast(false)检查
GC Sweep后,GUObjectAllocator.FreeUObject函数会回收掉这个UObject对象的内存。此时如果存在一个野指针(dangling pointer,空悬指针)指向该UObject,调用IsValidLowLevelFast(true)函数会返回false
注:野指针调用IsValidLowLevelFast函数本身是非法的,是未定义行为
注意:在PIE下执行GC没有效果,PC上需要在Standalone下执行
执行GC操作的函数
以阻塞的方式尝试进行一次GC Mark
GEngine->PerformGarbageCollectionAndCleanupActors();
TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, false); // ① 会先检查在其他线程中是否有UObject操作 ② 连续尝试没成功的次数 > GNumRetriesBeforeForcingGC时 注:UE4.25中GNumRetriesBeforeForcingGC配置为10
GEngine->ForceGarbageCollection(false); // 下一帧才以阻塞的方式尝试进行一次GC Mark
以阻塞的方式进行一次GC Mark
CollectGarbage(RF_NoFlags, false);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, false);
如果连续2次调用GC Mark,在第2次GC Mark之前,会先阻塞执行一次全量的GC Sweep
限制时间来分帧进行一次GC Sweep
IncrementalPurgeGarbage(true); // 以缺省0.002的时间进行一次GC Sweep
IncrementalPurgeGarbage(true, 0.1); // 以0.1的时间进行一次GC Sweep
引擎在每帧Tick中都在通过限制时间来分帧异步进行GC Sweep
阻塞的方式进行一次GC Sweep
IncrementalPurgeGarbage(false); // 以阻塞的方式进行一次GC Sweep
以阻塞的方式尝试进行一次全量的GC(包括Mark和Sweep阶段)
TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true);
GEngine->Exec(nullptr, TEXT("obj trygc"));
GEngine->ForceGarbageCollection(true); // 下一帧才以阻塞的方式尝试进行一次全量的GC
UKismetSystemLibrary::CollectGarbage(); // 下一帧才以阻塞的方式尝试进行一次全量的GC
以阻塞的方式进行一次全量的GC(包括Mark和Sweep阶段)
CollectGarbage(RF_NoFlags);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true);
GEngine->Exec(nullptr, TEXT("obj gc"));
GC相关的代理
static FSimpleMulticastDelegate& GetPreGarbageCollectDelegate(); // GC Mark或全量GC执行之前的代理通知
static FSimpleMulticastDelegate& GetPostGarbageCollect(); // GC Mark或全量GC完成之后的代理通知
static FSimpleMulticastDelegate PreGarbageCollectConditionalBeginDestroy; // GC Sweep ConditionalBeginDestroy之前的代理通知
static FSimpleMulticastDelegate PostGarbageCollectConditionalBeginDestroy; // GC Sweep ConditionalBeginDestroy完成之后的代理通知
static FSimpleMulticastDelegate PostReachabilityAnalysis; // GC Mark可达性分析之后的代理通知
GC相关的状态API
bool IsGarbageCollectingOnGameThread() // GC是否在游戏线程上
bool IsInGarbageCollectorThread() // 是否在GC线程上
bool IsGarbageCollecting() // 是否正在执行GC逻辑
bool IsGarbageCollectionWaiting() // GC是否在等待运行
GC锁
使得在垃圾回收时,其他线程的任何UObject操作都不会工作,避免出现一边回收一边操作导致的问题
FGCCSyncObject::Get().TryGCLock(); // 尝试获取GC锁
AcquireGCLock(); // 获取GC锁
ReleaseGCLock(); // 释放GC锁
bool IsGarbageCollectionLocked() // GC锁是否已经被获取了
{
FGCScopeGuard GCGuard; // 进入作用域获取GC锁,离开自动释放GC锁 非GameThread有效
Package = new FAsyncPackage(*this, *InRequest, EDLBootNotificationManager);
}
引擎中的GC逻辑
在Tick中调用GC逻辑
具体实现在:void UEngine::ConditionalCollectGarbage()函数中
在LoadMap中以阻塞的方式进行一次全量的GC
具体实现在:void UEngine::TrimMemory()函数中
手动调用ConditionalBeginDestroy
ConditionalBeginDestroy函数会主动先把对象的资源清理掉(将UObject的FLinkerLoad* Linker置nullptr),将UObject对象的Name设置成空,留下一个空壳UObject对象。后续分帧会立即回收掉UObject对象的内存。
这个函数相当于主动调用BeginDestroy(在对象GC时候也会调用),但是因为是Conditional的,如果之前已经调用过,在GC的时候就不会再次调用BeginDestroy从而避免了逻辑错误。
该方式的好处在于能及时回收UObject对象内存,不用等待GC时机的到来。
缺点是由于直接将UObject对象标记为RF_BeginDestroyed,跳过了GC Mark所有过程,也就没有自动更新引用。
此时即使其他UObject对象有UPROPERTY宏修饰的UObject*成员变量指向它也不会被更新引用置nullptr,导致野指针问题。
FString MyBPObjectPath = TEXT("/Game/ThirdPersonCPP/Blueprints/MyBlueprintObject.MyBlueprintObject_C");
UClass* MyBPObjectClass = LoadClass<UObject>(nullptr, *MyBPObjectPath); // MyBPObjectClass为UBlueprintGeneratedClass*类型
UMyBPObject* BPObj1 = NewObject<UMyBPObject>(this, MyBPObjectClass);
BPObj1->ConditionalBeginDestroy();
BPObj1 = nullptr;
----------------------------------------------------------------------------------------------------------------------------------
对于贴图(UTexture2D),材质(UMaterial)或网格(UStaticMesh、USkeletalMesh)等资源
在调用ConditionalBeginDestroy函数时,会先调用相应的函数将其Resource(大头)内存释放掉
释放Resource如果进行渲染命令队列的Flush可能产生卡顿,所以可以尽量只提交到渲染命令队列,虽然内存释放不那么及时,但也比gc快很多,大部分情况一帧内就会释放掉。
对于UActorComponent可以调用DestroyComponent,基本上能释放掉大部分资源。
对于AActor可以调用DestroyActor,基本上能释放掉大部分资源。
对于USceneComponent可以主动调用DestroyPhysicsState和DestroyRenderState释放掉物理或场景中的对象,也能立即释放一些内存。
----------------------------------------------------------------------------------------------------------------------------------
同步立即销毁UObject对象
以下方式完全绕开GC,来同步立即销毁UObject对象。这种方式的缺点与ConditionalBeginDestroy一样。
FString MyBPObjectPath = TEXT("/Game/ThirdPersonCPP/Blueprints/MyBlueprintObject.MyBlueprintObject_C");
UClass* MyBPObjectClass = LoadClass<UObject>(nullptr, *MyBPObjectPath); // MyBPObjectClass为UBlueprintGeneratedClass*类型
UMyBPObject* BPObj1 = NewObject<UMyBPObject>(this, MyBPObjectClass);
FString OldName = BPObj1->GetFullName();
BPObj1->ConditionalBeginDestroy();
bool bPrinted = false;
double StallStart = 0.0;
// Wait for the object's asynchronous cleanup to finish.
while (!BPObj1->IsReadyForFinishDestroy()) // 有一些对象如UTexture2D,需要GameThread等待RenderThread销毁FRenderResource资源,会卡住一段时间。非资源类的UObject对象IsReadyForFinishDestroy函数会立即返回true
{
// If we're not in the editor, and aren't doing something specifically destructive like reconstructing blueprints, this is fatal
if (!bPrinted)
{
StallStart = FPlatformTime::Seconds();
bPrinted = true;
}
FPlatformProcess::Sleep(0);
}
if (bPrinted)
{
float ThisTime = FPlatformTime::Seconds() - StallStart;
UE_LOG(LogTemp, Warning, TEXT("Gamethread hitch waiting for resource cleanup on a UObject (%s) overwrite took %6.2fms. Fix the higher level code so that this does not happen."), *OldName, ThisTime * 1000.0f);
}
// Finish destroying the object.
BPObj1->ConditionalFinishDestroy();
BPObj1->~UObject();
BPObj1 = nullptr;
UObject相关类图
可通过如下方法来获取FObjectArray GUObjectArray中FUObjectItem对象的个数 注:只增不减
int32 UObjectNum = GUObjectArray.GetObjectArrayNum(); UE_LOG(LogUObjectHash, Display, TEXT("Current UObject's Num: %d"), UObjectNum);
GC相关的设置
这些值的默认设置定义在Engine\Config\BaseEngine.ini中,项目修改这些值后,会保存在项目Config\DefaultEngine.ini中
[/Script/Engine.GarbageCollectionSettings] ; Placeholder console variable, currently not used in runtime. gc.MaxObjectsNotConsideredByGC=31552 ;NoGC对象长度 用于标记这个数组的前多少个元素要被GC跳过。在初始化时也预先在数组中添加了这么多个空元素 ; Placeholder console variable, currently not used in runtime. gc.SizeOfPermanentObjectPool=6321624 ; If enabled, streaming will be flushed each time garbage collection is triggered. gc.FlushStreamingOnGC=0 ;当指定为1时,在GC Mark或Full GC之前,先执行FlushAsyncLoading函数 ; Maximum number of times GC can be skipped if worker threads are currently modifying UObject state. gc.NumRetriesBeforeForcingGC=10 ; sed to control parallel GC. gc.AllowParallelGC=True ; Time in seconds (game time) we should wait between purging object references to objects that are pending kill. gc.TimeBetweenPurgingPendingKillObjects=60.000000 ; 地图内执行一次gc mark的间隔时间 ; Placeholder console variable, currently not used in runtime. gc.MaxObjectsInEditor=25165824 ; Maximum number of UObjects in the editor ; If true, the engine will destroy objects incrementally using time limit each frame gc.IncrementalBeginDestroyEnabled=True ;分帧执行UObject的BeginDestroy ; If true, the engine will attempt to create clusters of objects for better garbage collection performance. gc.CreateGCClusters=True ; Create Garbage Collector UObject Clusters(簇) ; Minimum GC cluster size gc.MinGCClusterSize=5 ; Whether to allow levels to create actor clusters for GC. gc.ActorClusteringEnabled=False gc.BlueprintClusteringEnabled=False ; Blueprint Clustering Enabled gc.AssetClustreringEnabled=False ; Whether to allow asset files to create actor clusters for GC. ; If false, DisregardForGC(跳过那些不用GC的对象) will be disabled for dedicated servers. gc.UseDisregardForGCOnDedicatedServers=False ; Use DisregardForGC On Dedicated Servers
注1:启动提示ObjectPool大小为6586750,超过了SizeOfPermanentObjectPool的配置值6321624,可提高SizeOfPermanentObjectPool数值,来减少GC扫描的Object数目
LogUObjectAllocator: Warning: |UObjectAllocator.cpp:36|6586750 Exceeds size of permanent object pool 6321624, please tune SizeOfPermanentObjectPool.
注2:FEngineLoop.PreInitPostStartupScreen执行调用GUObjectArray.CloseDisregardForGC()在这个时间点,遍历得到所有UObject(一共36425个),并将有效非蓝图的UObject设上Root标志,其中有5个是没有加上Root标志的UObject。
这些36425个UObject都会常驻内存,且会放到被GC忽略的Pool中(以此来不被GC遍历,提升GC遍历效率)
LogUObjectArray: |UObjectArray.cpp:158|36425 objects as part of root set at end of initial load.
LogUObjectArray: |UObjectArray.cpp:161|5 objects are not in the root set, but can never be destroyed because they are in the DisregardForGC set.
LogUObjectArray: |UObjectArray.cpp:173|CloseDisregardForGC: 36425/36425 objects in disregard for GC pool
GC相关的ConsoleVariable
;Placeholder console variable, currently not used in runtime. gc.MaxObjectsInGame ; int Maximum number of UObjects in cooked game ; Maximum number of UObjects for programs can be low gc.MaxObjectsInProgram ; int Default to 100K for programs ;If true, the UObjectArray will pre-allocate all entries for UObject pointers gc.PreAllocateUObjectArray ; bool 是否预分配ObjectArray,不配置缺省为false。若配置为true,会耗费更多内存,但可避免动态分配内存出现的卡顿 ;If true, the engine will free objects' memory from a worker thread gc.MultithreadedDestructionEnabled // 多线程析构UObject和释放其内存 ; If set to 1, the engine will attempt to trigger GC each frame while async loading. gc.StressTestGC // 非test包非shipping包才可用 ; If set to 1, the engine will force GC each frame. gc.ForceCollectGarbageEveryFrame // 设置为1时,每帧都会进行GC 非test包非shipping包才可用 ; Used to debug garbage collection...Collects garbage every frame if the value is > 0. gc.CollectGarbageEveryFrame // 设置为1时每帧都会进行GC,非常卡 ; Multiplier to apply to time between purging pending kill objects when on an idle server. gc.TimeBetweenPurgingPendingKillObjectsOnIdleServerMultiplier ; Time in seconds (game time) we should wait between purging object references to objects that are pending kill when we're low on memory gc.LowMemory.TimeBetweenPurgingPendingKillObjects ; Time in seconds (game time) we should wait between GC when we're low on memory and there are levels pending unload gc.LowMemory.TimeBetweenPurgingPendingLevels ; Memory threshold for low memory GC mode, in MB gc.LowMemory.MemoryThresholdMB ;Minimum number of objects to spawn a GC sub-task for. gc.MinDesiredObjectsPerSubTask ; Dumps count and size of GC Pools gc.DumpPoolStats ; Dumps all clusters do output log. When 'Hiearchy' argument is specified lists all objects inside clusters. gc.ListClusters ; Dumps all clusters do output log that are not referenced by anything. gc.FindStaleClusters ; Dumps references to all objects within a cluster. Specify the cluster name with Root=Name. gc.DumpRefsToCluster
Engine\Config\Android\AndroidEngine.ini中[/Script/Engine.GarbageCollectionSettings]标签下,用gc.MaxObjectsInGame=3000000来指定Android版游戏中允许的最大Object个数
Engine\Config\IOS\IOSEngine.ini中[/Script/Engine.GarbageCollectionSettings]标签下,用gc.MaxObjectsInGame=3000000来指定IOS版游戏中允许的最大Object个数
> UE4Editor-CoreUObject.dll!UObjectBaseInit() Line 1146 C++ UE4Editor-CoreUObject.dll!StaticUObjectInit() Line 4457 C++ UE4Editor-CoreUObject.dll!InitUObject() Line 4446 C++ [Inline Frame] UE4Editor-CoreUObject.dll!Invoke(void(*)() &) Line 51 C++ [Inline Frame] UE4Editor-CoreUObject.dll!UE4Tuple_Private::TTupleBase<TIntegerSequence<unsigned int>>::ApplyAfter(void(*)() &) Line 299 C++ UE4Editor-CoreUObject.dll!TBaseStaticDelegateInstance<void __cdecl(void),FDefaultDelegateUserPolicy>::ExecuteIfSafe() Line 731 C++ UE4Editor-Win64-DebugGame.exe!TMulticastDelegate<void __cdecl(void),FDefaultDelegateUserPolicy>::Broadcast() Line 955 C++ UE4Editor-Win64-DebugGame.exe!FEngineLoop::AppInit() Line 5788 C++ UE4Editor-Win64-DebugGame.exe!FEngineLoop::PreInitPreStartupScreen(const wchar_t * CmdLine=0x00007ff7a1865888) Line 2196 C++ [Inline Frame] UE4Editor-Win64-DebugGame.exe!FEngineLoop::PreInit(const wchar_t *) Line 3614 C++ [Inline Frame] UE4Editor-Win64-DebugGame.exe!EnginePreInit(const wchar_t *) Line 43 C++ UE4Editor-Win64-DebugGame.exe!GuardedMain(const wchar_t * CmdLine=0x0000022f82105d00) Line 128 C++ UE4Editor-Win64-DebugGame.exe!WinMain(HINSTANCE__ * hInInstance=0x000000000000000a, HINSTANCE__ * hPrevInstance=0x0000000000000000, char * __formal=0x0000000000000000, int nCmdShow=0) Line 275 C++ [Inline Frame] UE4Editor-Win64-DebugGame.exe!invoke_main() Line 102 C++ UE4Editor-Win64-DebugGame.exe!__scrt_common_main_seh() Line 288 C++ kernel32.dll!BaseThreadInitThunk() Unknown ntdll.dll!RtlUserThreadStart() Unknown
注:在启动时,会打印gc.MaxObjectsInGame、gc.MaxObjectsNotConsideredByGC和gc.SizeOfPermanentObjectPool的数值。
LogInit: |UObjectBase.cpp:1143|Presizing for max 3000000 objects, including 31552 objects not considered by GC, pre-allocating 6321624 bytes for permanent pool.
Android下定时每隔3s进行一次全量GC
@echo off setlocal EnableDelayedExpansion :endlessloop echo Garbage Collect... adb shell am broadcast -a android.intent.action.RUN -e cmd 'obj gc' ping /n 3 127.0.0.1>nul goto endlessloop
参考