Unity GC
本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
简略版本
在 Unity 中,垃圾回收(Garbage Collection,GC)采用的是基于标记-清除(Mark and Sweep)算法的自动内存管理机制。
基于标记-清除算法的垃圾回收包括以下步骤:
标记阶段(Mark): 在这个阶段,垃圾回收器会遍历程序中的对象,从根对象(如全局变量、活动线程的栈和静态变量等)开始,标记所有能够访问到的对象。这些被标记的对象被认为是“活跃”的,而未被标记的对象则被认为是“垃圾”。
清除阶段(Sweep): 在这个阶段,垃圾回收器会遍历整个堆内存,清除未被标记的对象,释放它们所占用的内存空间。这样就完成了垃圾的回收和内存的释放。
Unity 的垃圾回收器会自动管理内存,并在需要时执行垃圾回收。在游戏开发中,开发者通常不需要显式地调用垃圾回收,因为 Unity 的垃圾回收器会自动处理对象的分配和释放,以确保内存的有效使用和释放。
需要注意的是,虽然标记-清除算法是一种常见的垃圾回收算法,但它可能会产生一些性能上的开销,特别是在大型对象图和频繁的内存分配和释放时。因此,在编写 Unity 游戏时,需要注意内存的优化和对象的重复利用,以减少垃圾回收的频率,提高游戏的性能。
详细版本
在 Unity 游戏开发中,GC(垃圾回收)主要是为了自动管理内存,释放那些不再使用的对象所占的内存。当你在 Unity 中创建一个新的对象,如一个新的类、数组、列表或游戏对象,这需要占用部分内存。当这些对象不再被使用,例如从场景中移除、设置为 null、超出其活动范围等,它们就成为了 "垃圾",需要被清理。这个自动清理无用对象的过程就是垃圾回收。
Unity 的 GC 基于.NET 的内存管理和垃圾回收机制,其工作原理可以大致分为以下步骤:
-
标记:遍历所有的对象,找出哪些对象被引用,哪些对象没有被引用。具体做法是从根对象(例如全局对象、静态对象、栈上的对象)开始,寻找它们直接或间接引用的对象,被找到的对象被标记为 “活跃”。
-
清除:GC 清除那些被标记为 “非活跃” 的对象,也就是没有被任何地方引用的对象,这些对象占用的内存被认为是可以被安全回收的。清除的方式主要是将这部分内存标记为可用。
-
压缩:清除结束后,GC 会尝试将内存中的活跃对象压缩到一起,以减少内存碎片。这一步被称为压缩或整理。注意该步骤不是每次 GC 都会执行,因为它比标记和清除更耗时。
C# 的 GC 是自动进行的,也可以手动调用 GC.Collect() 进行垃圾回收,但只有在需要处理大量未被引用的对象,或者需要马上释放大量内存时才建议这么做,因为 GC.Collect() 对性能的影响很大。
值得注意的是,GC 操作是非常消耗 CPU 资源的,特别是对于实时运行的游戏应用来说,任何 GC 操作都可能导致游戏卡顿。所以在开发游戏时,我们应尽可能避免频繁触发 GC,在 Unity 游戏开发中,可以通过以下方法尽可能避免或减少 GC:
-
对象池:这是避免 GC 的最重要和最有效的方法。对象池的主要思想是重用对象,而不是创建新的对象并销毁旧的对象。例如,如果游戏中有很多敌人在不断地出现和消失,那么可以使用对象池来管理这些敌人的对象,而避免在每次需要一个新的敌人时都去创建一个新的对象。
-
减少或避免在频繁调用的函数中创建对象:例如,在 Update、FixedUpdate、LateUpdate 等函数中如果每次都创建新的对象,则很容易引起 GC。
-
避免使用 “+” 操作符拼接字符串:字符串是不可变的,使用 “+” 操作符拼接字符串会生成一个新的字符串对象,可以使用 System.Text.StringBuilder 来替代。
-
使用值类型代替引用类型:值类型如 struct 在栈上分配,不会产生垃圾。尽可能地使用值类型可以减少垃圾的生成。
-
尽量少使用 foreach 循环,在循环遍历的过程中,对于 IEnumerable 接口的对象,foreach 循环过程会产生垃圾。改用 for 循环是一种解决方法。
-
减少 Lambda 表达式和闭包的使用:当它们引用局部变量时会生成垃圾。
-
减少大型一次性内存分配,尽可能使用固定大小或者逐渐增长的数据结构。
-
对于仍需要的大对象,避免经常设为 null,设为 null 后会产生大量垃圾回收。
以上就是 Unity 游戏开发中 GC 产生的一些常见原因,理解和遵循这些原则可以实现有效的内存管理,编写更优化的代码,减少垃圾产生,提升游戏性能。