UE4GC简介
UE4为我们搭建了一套UObject对象系统,并且加入了垃圾回收机制,使我们用C++进行游戏开发时更加方便,而且游戏本身也可以极大程度的避免了内存泄漏问题。
UE4采用了标记-清扫垃圾回收方式,是一种经典的垃圾回收方式。一次垃圾回收分为两个阶段。第一阶段从一个根集合出发,遍历所有可达对象,遍历完成后就能标记出可达对象和不可达对象了,这个阶段会在一帧内完成。第二阶段会渐进式的清理这些不可达对象,因为不可达的对象将永远不能被访问到,所以可以分帧清理它们,避免一下子清理很多UObject,比如map卸载时,发生明显的卡顿。
GC发生在游戏线程上,对UObject进行清理,支持多线程GC。
对GC可以设置若干参数,比如MaxObjectsInGame,规定了游戏中最大存在的UObject对象(对编辑器不生效),移动平台上默认设置了131072,当UObject数量超过这个阈值时,游戏会崩溃,其他详细参数可见UGarbageCollectionSettings,GarbageCollection.cpp,UnrealEngine.cpp中相关的属性。
下图为标记-清扫的工作原理:
GC何时进行:
UE4中GC可以分为主动引发和自动引发两种方式
主动引发
可以在执行一些操作时手动调用GC,比如卸载一个资源后,立即调用一次GC进行清理。
而且方式有多种,游戏中可以调用ForceGarbageCollection来让World下次tick时进行垃圾回收。也可以直接调用CollectGarbage进行垃圾回收,引擎中大部分情况都用这种方式主动引发。
自动引发
游戏中,大部分的垃圾回收操作都是由UE4自动引发的,普通情况下不需要手动调用GC,这也是理想的GC使用方式。
当World进行tick时,会调用UEngine::ConditionalCollectGarbage()函数,函数中进行了一些判断,当满足GC条件时,才会执行GC。下面分析一下ConditionalCollectGarbage的执行逻辑。
UE4GC流程
入口为UObjectGlobals.h中定义的CollectGarbage()函数,如下:
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();
}
过程包括3个部分,获取GC锁,执行CollectGarbageInternal,释放GC锁。
获取GC锁
因为GC是多线程的,因此要设置GC锁,防止其他线程做UObject相关操作,会与GC冲突,这主要用于保护异步加载过程。
一个作用为防止一个对象被加载后,存储的变量还没来得及添加引用,就被当作不可达垃圾回收掉了。如下代码就是一个例子,FGCScopeGuard起到了阻止任何GC操作的作用。
FGCSyncObject
GC锁是一个广义的概念,其实是FGCSyncObject这个单例类,其内部封装了多个用于锁和同步的变量,可以用于在GC运行时阻塞其他non-game线程,也可以在non-game线程执行关键操作时阻塞GC线程。当然,也并非所有情况都会阻塞,当不能立即获取到GC锁时各个线程也可以根据具体逻辑执行其他内容。
主要成员变量如下:
FThreadSafeCounter AsyncCounter:是一个线程安全计数器,当由线程执行关键Async操作时,会对这个值进行增减
FThreadSafeCounter GCCounter:用作GC锁,不为0表示线程已经获取了GC锁,正在执行GC
FThreadSafeCounter GCWantsToRunCounter:这个计数器表示线程意向进行GC,但尚未获取到GC锁,Async线程没有自动强制实现这个逻辑,需要我们手动实现对这个变量的支持
FCriticalSection Critical:线程执行GC相关操作的关键区的保护&#x