最近项目测试然后发现有很多闪退的情况,用工具去检测了下确定是程序代码导致内存泄露的,然后就去找了发现了代码中存在下图中所示的写法(这只是个例子并不是实际情况)
开始的时候想当然了,认为按照引用计数这种原理,People这个对象创建之后,如果不手动把Hand中People对象置为NULL,然后再把People这个对象置为NULL是无法释放掉的,但是在我把这两个对象都置为null之后还是没有得到释放。我比较怀疑C#的GC机制和引用计数是一个东西?,然后就去查了下资料,这里附上云风的博客链接https://blog.codingnow.com/2008/06/gc.html,里面对比了GC和引用计数的区别。我简单介绍下。
GC与引用计数的区别:
GC在触发的时候会去遍历所有的对象,把所有的引用构成一张有向图,对于那些已经超出作用域死亡了的对象会打上清除标记,那些还在作用域内或者被其他对象引用的对象则不会打上标记。而对于互相引用的这种特殊情况,如果他们都已经不在作用域内且没有另外的外部变量引用,GC会把他们打上清除标记。但是引用计数并不是这样,引用计数会对所有引用进行计数,当对象的引用为0的时候才会去释放这个对象,和unity中Assetbundle的加载做的资源引用计数差不多,所以引用计数就会有一个软肋,就是处理不了我上图所说的情况,所以people对象是释放不掉的。
在知道GC的原理之后,我做了一个实验去验证了下。
用的Unity的空场景,项目里面什么都没,可以看见创建1W个对象Mono内存没有什么变化。而且从图中波峰可以看到那里触发了GC,所以GC是可以回收掉互相引用的情况。所以开始的怀疑泄露的地方就错了,需要重新去查找。后面发现People事件注册了一些事件,但是这些事件没有注销,根据经验把People和Hand中注册的事件都注销掉了,这时候再用profiler检测发现内存被释放掉了,个人认为是事件拿到了People的引用,但是按照我上面说的即使注册了事件形成了相互引用的关系,毕竟这个事件,所以就去测试了下。测试代码如下
结果得到了验证,但是我对事件的机制不是很了解,所以就去查了下,这里附上相关链接http://www.cnblogs.com/murongxiaopifu/p/4684728.html,事件会把当前注册事件的对象到自身的Target变量上,并保存注册函数的句柄,所以事件也会对当前对象造成引用关系,所以在这里提醒大家,虽然GC是很智能的系统,在平时注意事件注册与注销成对出现,以及在成员变量,容器的使用上要额外注意,否则会出现内存泄露的问题。