Unity中的垃圾回收(GC)机制



一:前言

GC就是Grabage Collector,当没有任何栈内存所指向的堆内存空间,所有的垃圾将被GC不定期进行回收并且释放无用内存空间,使这些内存可以再次使用,但是如果垃圾过多将影响到GC的处理性能,从而降低整体的程序性能,在实际开发之中,对于垃圾的产生越少越好
对应的方法是GC.Collect,其功能就是强制对所有垃圾进行回收


二:Unity中的内存管理

Unity是自动内存管理,Unity中可以访问两个内存池:栈内存和堆内存,栈用于短期存储的小数据,堆用于较长时间存储的较大的数据块。当创建变量的时候,Unity会从栈或堆中请求一个内存块,只要变量值作用域内(仍可被代码访问),分配给它的内存就会保留。当变量不在作用域范围内了,这块内存就不再需要了,可以被创建它的那个内存池回收。只要变量的引用超出了作用域,栈内存就会被重新分配,而堆的内存不同,堆内存不会重新分配即使变量的引用超出了作用域,垃圾回收器会标识没有使用的堆内存,之后定期清理堆内存



三:堆内存分配步骤

——Unity检测是否有足够的闲置内存单元用来存储数据,如果有,则分配对应大小的内存单元
——如果没有足够的存储单元,Unity会触发垃圾回收(GC)来释放不再被使用的堆内存。这步操作是一步缓慢的操作,如果GC后有足够大小的内存单元,则进行内存分配
——如果GC后并没有足够的内存单元,Unity会扩展堆内存的大小,这步操作也会很缓慢,然后分配对应大小的内存单元给变量


四:为什么要避免产生GC?

GC是一个极其消耗性能的工作,每次都需要遍历整个堆内存,堆内存上的变量或者引用越多其运行的操作会更多,耗费的时间就越长



五:什么情况下会产生GC?

登录后复制

class Person{    String name;         int age;  }public class JavaDemo{    public static void main(String args[])    {        Person per1 = new Person();           Person per2 = new Person();                per1.name = "liu";        per1.age = 11;                per2.name = "yin";        per2.age = 12;                per2 = per1;        per2.age = 100;    }}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.


例如上面这段代码,当实例化per1和per2时会分配内存,当将per1的引用给per2时,per2分配的内存就变成了内存垃圾,这时候这段内存就会被被标记为垃圾之后在某个时候自动被释放掉


六:GC何时会被触发?

——堆分配时堆上的可用内存不足时触发GC
——GC会不时的自动运行(频率因平台而异)
——手动强制调用GC.Collect



七:如何避免频繁产生GC造成性能问题?

——减少频繁分配内存
避免在Update、LateUpdate等每帧调用的函数中频繁分配内存


登录后复制

void Update()  {      List<int> list = new List<int>();      Fun(list);}1.2.3.4.5.


上面这种写法,每次new都会分配一次内存


登录后复制

private List<int> list = new List<int>();  void Update()  {      list.Clear();      Fun(list);}1.2.3.4.5.6.


上面这种写法只有在容器被创建或者扩容时才会有堆分配,从而减少了垃圾内存的产生

——运用对象池
在运行时大量对象的创建和销毁依然会引起GC问题,用对象池技术可以让对象复用而不是重复的创建和销毁

——字符串
在C#中,String是引用类型,它的值是不可变的,一旦被初始化后就不能改变其内容,频繁的修改字符串建议使用StringBuilder
​C#中的String和StringBuilder​​

——Unity函数的调用

例如GameObject.name或GameObject.tag会返回一个新的字符串,意味着频繁调用会产生内存垃圾,可以使用CompareTag代替


登录后复制

private string playerTag = "Player";  void OnTriggerEnter(Collider other)  {      bool isPlayer = other.gameObject.tag == playerTag;}1.2.3.4.5.


上面这种写法每次访问GameObject.tag会产生内存垃圾


登录后复制

private string playerTag = "Player";  void OnTriggerEnter(Collider other)  {      bool isPlayer = other.gameObject.CompareTag(playerTag);}1.2.3.4.5.


上面这种写法不会产生内存垃圾

——装箱
值类型转换为引用类型的过程称为装箱,装箱会产生GC

——协程
避免yield return 0,因为会产生GC,因为int类型的0被装箱,而使用yield return null替代则不会产生装箱操作
还比如在协程中避免多次new同一个WaitForSeconds对象


登录后复制

while (!isComplete)  {      yield return new WaitForSeconds(1f);}1.2.3.4.


上面这种写法每次循环都会分配一次新内存


登录后复制

WaitForSeconds delay = new WaitForSeconds(1f);  while (!isComplete) {    yield return delay;}1.2.3.4.5.


上面这种写法只会分配一次内存

——Linq表达式
LINQ和正则表达式由于在后台会有装箱操作而产生垃圾,在有性能要求的时候最好不使用



免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空