托管堆:由Mono分配与管理。而托管的 意思是可以自动改变堆的大小,适应内存的需要,适合时机调用GC释放内存。
Unity内存管理机制
1、Unity存在2个内存管理区域,堆和堆栈,堆栈一般存储小和短暂的数据,堆存储大和时间长的数据
2、堆和堆栈之间差异:堆栈分配与回收较快。而堆在分配内时,先检查是否有内存,如果没有,将会进行GC,堆在GC后内存仍然不足时,将会申请内存,但是GC所释放的内存,将会留给Mono,而不会返还给操作系统。GC时,会暂停那些需要Mono内存分配的线程。其中在GC和内存的扩展时很慢
问题:对于Mono内存的申请机制不了解,就不知道为什么要先申请大内存的,然后再申请小内存
答:因为先申请小的内存,再去申请大的内存时候,空闲内存肯定是不够的,所以将会出发GC或者内存扩展
GC
1、值类型变量在堆栈内存上分配,其他类型变量在推内存上分配
函数结束,立即回收
void Func(){
int a = 1;
}
函数结束,不会立即回收,需要等待GC
void Func(){
List<int> lists = new List<int>();
}
2、相关的解决方法:
- 常用的函数中 尽量不要去进行内存的分配,利用缓存代替,比如函数中分配数组,可以选择用在外部定义一个缓存数组
- Unity的Update中分配内存会产生很大的内存垃圾,可以考虑设置定时或者判断减少产生内存分配函数的调用
常见的不必要的内存分配
1、字符串,字符串在C#是类,并且字符串在创建完后将不可被改变,一旦改变相当于创建了一个新的字符串对象,原来的将会废弃,可以直接采用StringBuilder拼接。并且Debug.Log会造成很大的内存垃圾
2、Unity某些函数会产生内存垃圾
- 如果unity中函数返回数组,那么每次都会分配一个数组,如下的循环就是如此
void Func(){
for(int i=0; i<mesh.normal.Lenght; i++){
Vector3 vec = mesh.normal[i];
}
}
- 其中gameObject.name或者gameObject.tag, 都将返回字符串,这样会造成不必要的内存垃圾。其中gameObject.CompareTag()。可以有效的避免内存垃圾
装箱操作
1、装箱操作就是将值类型被用作引用类型之间的变换。比如我们向需要引用类型参数的函数传入值类型,这时将会触发装箱操作。
2、对于装箱操作,将会为值类型分配一个System.Object对象,这样就多了不必要的引用类型,从而造成不必要的内存消耗。
协程
1、如yield return 0将会触发装箱操作,利用yield return null代替
2、yield return new WaitForSeconds(1)每次将会创建一个新对象。所以我们可以提前缓存一个这样的对象,从而放置每次都new一个新对象。
其他情况
1、结构体Struct中如果包含引用类型变量,那么GC将会检查这个结构体。所以使用上需要注意
2、当一个对象内包含对另一个对象的引用,那么GC将不得不去检查
public class Person{
private Person neighbor;
xxx
}
重构后, 最后通过Id得到相应的结果
public class Person{
private int neighborId;
xxx
}
3、标记清除法,先暂停进程中所有线程,然后将所有对象全部标成垃圾,然后从根对象根据引用遍历然后纳入对象图中,最后没有被纳入对象图的对象将会被清除。
4、分代0/1/2 越新的对象代数越靠前,GC后未被清理的将会被纳入下一代,每一代的GC不同0/1/2分别对应100:10:1,代数越老越回收频率越低。
5、非托管资源怎么回收,可以放到析构函数中,但是析构函数的调用有不确定性,为了有效的回收资源,所以有了IDisposable接口,可以手动或者using回收非托管资源。