前言


最近因为考试有段时间没来更新了,不过也是我自己做的游戏已经开始进入到堆动作还有玩法的阶段了,所以没什么研究其他东西(除了AI系统)也就没怎么更新,不过突然想了想可以讲下GC回收机制(就是垃圾回收)来科普下。这次的文章代码量会比较少,因为主要是来科普下的而已。

那么废话少说直接开始吧。

 

回收机制的解释


 

开始前我先来解释下回收机制是为什么产生和有什么用处的,首先垃圾回收机制是每个编程语言都会存在的操作,不过每种语言不同回收的原理和方法是不同的,比如C/C++的垃圾回收是系统自动回收的,比如一个变量的生命周期结束就会触发回收,但其实回收的原理是栈的变化,因为C/C++的临时变量是保存在栈上的,通过进栈和出栈就可以完成回收;

但是Java的回收虽然也是按生命周期的结束来触发回收机制,但其原理是通过释放JVM虚拟机的地址空间来进行回收的,这个是因为在Java中每个变量其实是在JVM中申请了一个地址来保存的(因为主题不是讲Java的所以就粗略的说下而已,实际情况会有点多就不讲了)。

回收机制的操作是为了避免内存空间的低使用率带来的内存溢出,打个比方,你电脑内存有4GB,但是如果我一直申请内存空间(就是声明一个变量)按一个变量是int类型来看可以声明1,073,741,824个变量,是不是看起来很多?但要注意,电脑的内存是有保留栈和代码栈,数据栈,文本栈的,每次声明的变量会保留在数据栈中,当栈溢出的时候就会上溢到其他的栈中破坏栈中本来的数据,同时如果是按函数来循环声明的话,函数的返回地址也是保存在栈中的,当数据溢出时就会破坏函数的返回地址,当函数回调的时候就会将EIP(RIP)指针指向被溢出的地址导致程序崩溃。

嗯….好像有点硬核了,反正翻译成人话就是回收机制可以有效避免程序的崩溃和内存溢出。

虚幻引擎中的回收机制


 

虚幻引擎中的回收机制是与UObject这个类相关的,这个其实说实话和Java中的Object类很像,虚幻引擎所有的类都是UObject类的派生类型,就是说所有的类都是继承与UObject这个类的。

虚幻的回收机制是按标记-清扫垃圾回收这个机制来的,说人话就是我标记了一个可以回收的对象,等下一次我再来的时候就顺手把那个对象给清除了,其过程分成2个阶段,第一阶段是从一个根集合出发,遍历所有可达对象,遍历完成后就能标记出可达对象和不可达对象了,这个阶段会在一帧内完成,这个就是标记;第二阶段是多次的清理不可达对象,为什么要多次清理呢?这个是因为避免一次性清除太多对象导致的内存空余过多(嗯….这个内存空余空间我感觉到时候可以单独开个关于内存操作的文章来说下)。

这里稍微说点关于虚幻引擎UObject对象的事情,在一个关卡(或者说游戏)中可以声明的UObject对象是有限的这个可以在CoreSettings.cpp中看到

UGarbageCollectionSettings::UGarbageCollectionSettings()
: Super()
{
	SectionName = TEXT("Garbage Collection");

	TimeBetweenPurgingPendingKillObjects = 60.0f;
	FlushStreamingOnGC = false;
	AllowParallelGC = true;
	IncrementalBeginDestroyEnabled = true;
	MultithreadedDestructionEnabled = true;
	NumRetriesBeforeForcingGC = 0;
	MaxObjectsNotConsideredByGC = 0;
	SizeOfPermanentObjectPool = 0;
	MaxObjectsInEditor = 12 * 1024 * 1024;
	MaxObjectsInGame = 2 * 1024 * 1024;	
	CreateGCClusters = true;	
	MinGCClusterSize = 5;
	AssetClusteringEnabled = true;
	ActorClusteringEnabled = true;	
	BlueprintClusteringEnabled = false;
	UseDisregardForGCOnDedicatedServers = false;
}

在Windows环境下是最多2*1024*1024个对象,其他的环境可以自己看。

触发回收的方式和Java也是很像,分为主动触发和被动触发,当使用主动触发的时候可以使用ForceGarbageCollection这个函数来在下一次Tick的时候回收,或者可以使用CollectGarbage函数来直接回收。

被动回收的话其实是最理想的状态,当World进行Tick时,会调用UEngine::ConditionalCollectGarbage()函数,函数中进行了一些判断,当满足GC条件时,才会执行GC。这个函数因为代码有点长就不贴出来了,就简单说下流程;

首先是先判断是否已经在回收过程中了,然后是判断是否调用了ForeGarbageCollection函数,继续判断World是不是已经执行了BeginPlay函数,如果执行了的话每隔60秒再次执行一次回收但如果内存剩余量低于预设的值的时候就会每隔30秒执行一次回收。

结语


又水完一篇文章了,最近确实找不到什么好说的东西出来,但写完这篇文章后突然感觉可以说下内存的操作和一些比较有意思的东西,不过还是等我把UE4的AI系统搞懂了以后再说了吧(狗才摸鱼)。那么下次见。