目录
三、检测堆内存分配:Unity profiler window
一、GC简介
在游戏运行的时候,数据主要存储在内存中,当游戏的数据在不需要的时候,存储当前数据的内存就可以被回收以再次使用。内存垃圾是指当前废弃数据所占用的内存,垃圾回收(GC)是指将废弃的内存重新回收再次使用的过程。
Unity中将垃圾回收当作内存管理的一部分,如果游戏中废弃数据占用内存较大,则游戏的性能会受到极大影响,此时垃圾回收会成为游戏性能的一大障碍点。
1. Unity内部有两个内存管理池:堆内存和栈内存
堆内存(heap) 主要用来存储较大的和存储时间较长的数据,主要是负责程序中的对象和数据。
栈内存(stack) 主要用来存储较小的和短暂的数据,主要是负责运行时的代码,例如函数调用。
2. 哪些数据在堆和栈上?
栈->值类型: bool byte char decimal double enum float int long sbyte short struct uint ulong ushort
堆->非空引用类型: class interface delegate object string,同时包含:
1) 值类型数组
2) 装箱的值类型
3. Unity托管堆简介
托管堆是由项目的脚本运行时(Scripting Runtime)——Mono或者IL2CPP内存管理器管理的一个内存片段:底层都是在C++分配内存。
(1) Unity Mono内存是只升不降,即使用后,哪怕空闲再多,也不会还给系统
(2) Il2cpp,则与一般的C++内存释放一样,释放的内存都会还给系统
4.Unity的GC机制
使用了Boehm GC算法(可以参考:https://en.wikipedia.org/wiki/Boehm_garbage_collector),是非分代(non-generational)和非压缩(non-compacting)的。
1) "非分代"是指GC执行清理操作时,必须遍历整个内存,去标记哪些没有被引用并且删除,随着内存的增长,它的性能就会降低。 目前2019版本的unity在实验分代GC算法
2) “非压缩”意味着内存中的对象不会被重新定位,去减小对象之间的内存空隙
1. 堆内存分配和回收机制
堆内存上的内存分配和存储相对而言更加复杂,主要是堆内存上可以存储短期较小的数据,也可以存储各种类型和大小的数据。其上的内存分配和回收顺序并不可控,可能会要求分配不同大小的内存单元来存储数据。
堆上的变量在存储的时候,主要分为以下几步:
1)首先,unity检测是否有足够的闲置内存单元用来存储数据,如果有,则分配对应大小的内存单元;
2)如果没有足够的存储单元,unity会触发垃圾回收来释放不再被使用的堆内存。这步操作是一步缓慢的操作,如果垃圾回收后有足够大小的内存单元,则进行内存分配。
3)如果垃圾回收后并没有足够的内存单元,则unity会扩展堆内存的大小,这步操作会很缓慢,然后分配对应大小的内存单元给变量。
堆内存的分配有可能会变得十分缓慢,特别是在需要垃圾回收和堆内存需要扩展的情况下,通常需要减少这样的操作次数。
2. 垃圾回收时的操作
当堆内存上一个变量不再处于激活状态的时候,其所占用的内存并不会立刻被回收,不再使用的内存只会在GC的时候才会被回收。
每次运行GC的时候,主要进行下面的操作:
1)GC会检查堆内存上的每个存储变量;
2)对每个变量会检测其引用是否处于激活状态;
3)如果变量的引用不再处于激活状态,则会被标记为可回收;
4)被标记的变量会被移除,其所占有的内存会被回收到堆内存上。
GC操作是一个极其耗费的操作,堆内存上的变量或者引用越多则其运行的操作会更多,耗费的时间越长。
注意:包含引用类型的结构体:struct是值类型的变量,但是如果struct中包含有引用类型的变量,那么GC就必须检测整个struct,大大增加GC工作量。
3. 何时会触发垃圾回收?
主要有三个操作会触发垃圾回收:
1) 在堆内存上进行内存分配操作而内存不够的时候都会触发垃圾回收来利用闲置的内存;
2) GC会自动的触发,不同平台运行频率不一样;
3) GC可以被强制执行。
特别是在堆内存上进行内存分配时内存单元不足够的时候,GC会被频繁触发,这就意味着频繁在堆内存上进行内存分配和回收会触发频繁的GC操作。
4. GC操作带来的问题
《1》.GC操作会需要大量的时间来运行,如果堆内存上有大量的变量或者引用需要检查,则检查的操作会十分缓慢,这就会使得游戏运行缓慢。
《2》GC可能会在关键时候运行,例如在CPU处于游戏的性能运行关键时刻,此时任何一个额外的操作都可能会带来极大的影响,使得游戏帧率下降。
《3》另外一个GC带来的问题是堆内存的碎片划。当一个内存单元从堆内存上分配出来,其大小取决于其存储的变量的大小。当该内存被回收到堆内存上的时候,有可能使得堆内存被分割成碎片化的单元。也就是说堆内存总体可以使用的内存单元较大,但是单独的内存单元较小,在下次内存分配的时候不能找到合适大小的存储单元,这也会触发GC操作或者堆内存扩展操作。
堆内存碎片会造成两个结果,一个是游戏占用的内存会越来越大,一个是GC会更加频繁地被触发。
二、 GC优化
1.降低GC影响的方法
《1》 减少GC的运行次数:减少托管堆内存的分配频率<