简介
当游戏运行时,使用内存存储数据。当数据不再需要时,存储这些数据的内存被释放,以便重新使用。我们把已经存储了数据,但是已经不再使用这些数据的内存叫做垃圾。我们把重新使得这些存储垃圾的内存变的可用的过程叫做垃圾回收。
Unity使用垃圾回收作为内存管理的一部分。如果垃圾回收执行的太频繁或者垃圾太多,那么我们的游戏可能会性能较差。这意味着垃圾回收是很常见的引起性能问题的原因。
在这篇文章中,我们将学习垃圾回收怎样工作,在什么时间发生,以及怎样有效率的使用内存以便把垃圾回收对我们游戏的影响降到最低。
Unity内存管理概述
为了理解垃圾回收怎么样工作以及什么时候发生,我们必须首先理解内存使用在Unity中是怎么工作的。Unity管理内存的方法叫做自动内存管理。这意味着我们的代码不需要明确的告诉Unity怎么样在细节上去管理内存。Unity替我们做了这些。
在本质上,Unity中的自动内存管理是这样工作的:
-Unity在两种内存池中存取:栈内存和堆内存。栈用来存储短期的和小块的数据,堆用来存储长期的和大块的数据。
-当一个变量创建时,Unity在栈或堆上申请一块内存。
-只要变量在作用域内(仍然能够被我们的代码访问),分配给它的内存表示在使用中。我们称这块内存已经被分配。我们描述一个变量被分配到栈内存为栈上对象,一个变量被分配到堆内存为堆上对象。
-当变量不在作用域了,内存不再被需要了,就可以被返回到它被申请的内存池。当内存被返回到内存池时,我们称之为内存释放。当变量不在作用域内时,栈上的内存会立刻被释放。而当堆上的变量不在作用域时,在堆上的内存并不会在这一刻马上被释放,并且此时内存状态仍然是已被分配状态。
-垃圾回收器识别并且释放无用的堆内存。垃圾回收器周期性的清理堆。
现在我们清楚了流程,让我们更近一步,看看在栈上分配释放内存和在堆上分配释放内存的区别。
栈上分配和释放内存时发生了什么
栈上分配和释放内存很快并且很简单。这是因为栈上只是用来存储小数据且很短的时间。分配和释放内存总是按照预期的顺序和预期的大小。
栈工作像栈数据类型一样:它是一个一些元素的简单集合,在这里是一些内存块,元素智能按照严格的顺序添加或者移除。因为这种简洁和严格,所以很快:当一个变量存储在栈上时,内存简单的在栈的“末尾”被分配,当栈上的变量不在作用域时,存储它的内存马上被返还回栈以便重用。
当堆上内存分配时发生了什么
堆上分配内存比栈上要复杂很多。因为堆上会存储长期和短期的数据,并且数据有很多不同的类型和大小。堆上内存的分配和释放并不总是有预期的顺序,并且可能需要不同大小的内存块。
当一个堆变量被创建,会发生以下步骤:
-首先Unity必须先检测堆上是否有足够的空闲内存。如果堆上空闲内存足够,那么为变量分配内存。
-如果堆上内存不足,Unity触发垃圾回收器尝试释放堆上无用的内存。这个操作可能会比较慢。如果现在空闲内存足够了,那么为变量分配内存。
-如果执行垃圾回收后,堆上空闲内存仍然不足,Unity会增加堆内存容量。这操作可能会比较慢。这样就可以为变量分配内存了。
堆上分配内存可能会很慢,尤其是需要执行垃圾回收和扩展堆内存时。
垃圾回收时发生了什么
当堆变量不在作用域后,存储它的内存不会马上被释放。只有执行垃圾回收时,堆上的无用内存才会被释放。
每次垃圾回收执行时,会发生如下步骤:
-垃圾回收器检查堆上的每个对象。
-垃圾回收器查找所有当前对象的引用,确认堆上对象是否还在作用域。
-任何不在作用域的对象被标记为待删除。
-被标记的对象被删除掉,且把为他们分配的内存返还到堆中。
垃圾回收可能是昂贵的操作。堆上的对象越多,它需要做的工作就越多;我们代码里对象的引用越多,它需要做的工作就越多。
垃圾回收什么时候发生
三件事情可能会触发垃圾回收:
-当堆上分配内存时,且空闲内存不足,会触发垃圾回收。
-垃圾回收随着时间自动触发(虽然频率由平台决定)。
-我们可以手动强制执行垃圾回收。
垃圾回收可能会很频繁。因为当堆上分配内存时,且空闲堆内存不足,会触发垃圾回收,这意味着频繁的分配释放堆内存可能会导致频繁的垃圾回收。
垃圾回收带来的问题
现在我们理解了垃圾回收在Unity内存管理中扮演的角色,我们可以考虑可能会遇到哪些类型的问题了。
最明显的问题是垃圾回收会花费一部分时间去执行。如果垃圾回收器有很多堆上的对象需要检查,并且很多对象的引用需要检查,检查所有这些对象可能会比较慢。这可能会引起游戏卡顿或运行慢。
另一个问题是垃圾回收可能在不恰当的时机执行。如果cpu已经努力工作在我们游戏的性能临界的部分了,这时甚至垃圾回收引起的一点额外消耗可能会引起帧率下降和性能的明显改变。
另一个不太容易被注意到的问题是堆碎片化。当内存在堆上被分配时,是在空闲空间中根据要被存储的数据大小分块的。当这些内存块被返还回堆内存时,堆内存中的空闲空间被分成了一些碎块。这意味着,也许堆上总的空闲很大,但是我们不能直接分配大块内存给需要的变量,因为没有足够大的内存块了,除非执行垃圾回收或者扩展堆内存。
碎片化的堆内存会带来两个影响。首先,我们游戏使用的内存占用比实际需要的高,其次,垃圾回收会更频繁的执行。
诊断垃圾回收问题
垃圾回收引起的性能问题,可能表现出来为低帧率,性能不稳定或者是间歇性的卡死。如果你的游戏性能问题表现如此,那么首先你应该使用Unity Profiler去确认问题是否真的由垃圾回收造成。
学习怎么使用Profiler诊断性能问题,请阅读