暂时写下我现在知道的,以后有更深的理解再补充,写的不对的欢迎留言指出。
查了一些资料,GC内存泄漏我觉得可以分为狭义的内存泄漏和广义的内存泄漏吧,狭义的泄漏是因为某些认为原因,GC未能履行其职责清除内存,比如编码的时候不小心忘记清除没用的静态共有对象,使得他一直占有没用内存。广义的泄漏就是真的就泄漏了,主要是非托管类型。
对于第一种情况我知道的几种泄漏的方式:
1、静态对象没有及时释放。静态对象在程序运行期间都占据内存。这个怎么说,我觉得除了一些工具类和管理器其他部分应该尽量避免静态字段吧,如果模块间要通过静态字段通讯,那耦合度肯定是有点高的。
2、事件。之前实习过看到框架各种OnDisable清除事件内的监听者,那时候可能搜索的姿势不对查不到什么资料,现在终于是明白了。事件导致的内存泄漏可以看一下这种情况:在函数中给全局事件添加一个监听者,甚至还引用了函数内的局部变量,因为C#中有闭包的机制,所以这个本应该在函数结束就被清除的局部变量会被写入监听者内部,也就是他是一直被引用了,如果忘记清除事件,这个引用将一直存在,导致所谓的内存泄漏。
3、多线程。每个线程分配了私有栈和内核栈,就算不用也是占据内存的。多线程的泄漏可以是开了一个无限运行的线程而且忘了关闭,自身堆栈占据内存,甚至线程可能是一个用来处理独立事件的线程,比如点击事件,每点击一次如果需要开启新的线程或者定义一些新的对象来存储数据,那么这个不仅是泄漏,而且泄漏的内存还会随点击的次数慢慢增大。
对于第二种情况:
主要是非托管类型,非托管类型可以通过实现IDisposable接口清除,如果使用终结器清除是有风险的。
class Test
{
~Test()
{
while (true)
{
Console.WriteLine("Destructor...");
}
}
}
class Test2
{
~Test2()
{
Console.WriteLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...");
}
}
class Program {
static void Te()
{
Test a = new Test();
a = null;
GC.Collect();
}
static void Te2()
{
Test2 a = new Test2();
a = null;
GC.Collect();
}
static void Main()
{
Te2();
Te();
Thread.Sleep(10000);
}
}
在这个代码中,我在其中一个终结器弄了个死循环,结果是终结器会一直执行,之后就不执行了,猜测可能是有超时的机制,但是可怕的是Te2中Test2的终结器并没有被执行。所以又可能不是超时机制,而是专门调用终结器的线程崩了然后直接重新开始???于是我修改了代码变成下面这样:
我希望Te3中把处理终结器的线程搞崩然后再Main中恢复并调用Te2中Test2对象的终结器,结果还是没有执行。。。这里以后学多点会了再补充吧,多使用IDisposable来释放非托管类型,但是如果手动调用dispose,如果在dispose之前出现异常, 后面的dispose就没机会调用了,为此可以使用try-finally来保证dispose一定执行,也可以用using,他的DIL代码跟try-finally是一样的。