Unity中的GC

Posted Hello Bug.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
	

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


六:GC何时会被触发?

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


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

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

void Update()  
  
    List<int> list = new List<int>();  
    Fun(list);

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

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

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


——运用对象池
在运行时大量对象的创建和销毁依然会引起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;

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

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

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


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


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

while (!isComplete)  
  
    yield return new WaitForSeconds(1f);

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

WaitForSeconds delay = new WaitForSeconds(1f);  
while (!isComplete) 
  
  yield return delay;

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


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

以上是关于Unity中的GC的主要内容,如果未能解决你的问题,请参考以下文章

Unity中的GC

Unity ❉ 使用心得 ☀️| Unity中的 GC及优化 超级全面解析 ☆(ゝω・)v 建议收藏!

Unity优化之GC——合理优化Unity的GC

Unity中的GC以及优化

Unity优化之GC——合理优化Unity的GC (难度3 推荐5)

Unity优化之GC--合理优化unity的gc