GC的功能:对标记为未使用的内存进行回收,这个过程还会对余下的对象进行标记分级,内存整合。
GC的产生:堆分配时堆上的可用内存不足时触发GC,根据GC的机制。只要我们申请对堆内存,就可能 触发GC.
GC的影响:GC需要检查堆上的对象是否有效,如果检查的对象很多将费时费力。这导致游戏卡顿或缓慢运行
Unity中会申请堆内存的情况
- new 对象
- 容器扩容时,如List、Dictionary
- 启用协程时
- 生成闭包时
- 某个函数返回数组、List等容器对象时
- 字符串拼接时
- IO时
- 装箱时
- GameObject.tag、GameObject.name
- 使用变长参数数组
- 发生UGUI重构
- 还有一些插件内部堆内存申请
如何避免上面的这些情况
- 针对new class()
可以考虑用struct代替,如果只是数据的存储
如果不行,且频繁创建可以考虑缓存起来,或者使用static,弄对象池等。
2.针对容器扩容
应该减少容器扩容的次数
对容器进行初始化设置容量(初始值大小要看实际情况)
List使用AddRange()代替Add()
Dictionary扩容按HashHelpers类中的primes数组走
3.启用协程时
每次StartCoroutine都会生成一个对象
协程内部使用的临时变量越多,GC就越高
所以===》
频繁创建的协程考虑使用Update替代
减少协程内部临时变量的使用
对new WaitForEndOfFrame() 、new WaitForSeconds(1.0f)等进行统一 cache
使用第三方插件,如MEC
4.生成闭包时
尽可能避免匿名函数引用外部变量,让其可被静态化
搞清楚哪些变量被匿名函数引用了,防止内存泄漏
尽量把被引用的变量声明放在后面,用变量复制来延迟匿名函数创建
尽量不用Linq语句
5.函数返回数组、List等容器对象时
减少调用频率,如下
使用传入代替返回,如下
6.字符串拼接时
可以将动态文字与静态文字进行分离使用
减少拼接次数
重复使用一个字符串拼接,考虑用stringbuilder
7.IO时
对资源进行缓存使用
8.装箱时
1、降低数值类型转string类型
2、Enum或 struct作为 Dictionary的 Key,或List的元素,
在List的Contains()、Dictionary的ContainKey()时,会触发Equals造成装箱,解决
对于struct,override GetHashCode并实现 IEquatable<T>
对于Enum,直接使用int
或者Enum在作为Dictionary Key时,为Enum定义一个专用比较器。如下
9.使用GameObject.Tag
使用CompareTag()代替
10.函数使用变长参数传参
函数传参时,使用可变参数传参会自动转换成 params Type[] args,所以最好的建议是不用
11.UGUI网格重建时
UI动静分离
12.一些插件内部堆内存申请
使用插件前,先用Profiler跑跑看,有没有GC产生