GC
废弃对象的4种方式
方式 | 例子 | 被释放的对象 |
---|---|---|
将null引用赋值给对象的仅剩引用。(赋空值) | ClassA a = new ClassA();a=null; | 没有一个引用引用它时 |
将不同对象的引用赋值给对象的仅剩引用。(赋新值) | a1 = new ClassA();a2 = new ClassA(); a1 = a2; | a1的原先引用对象 |
包含对象A的仅剩引用的对象B被废弃。(主人被废弃) | b = new ClassB();a = new ClassA(b);b = null; | a随着b的引用对象废弃而废弃 |
方法调用完返回原作用域时,方法调用过程中的引用越出了作用域。(临时变量) | void funa(){ ClassA a = new ClassA();} | funa调用结束后a被废弃 |
为了避免占用大量CPU处理时间,GC占用尽量少的CPU时间来确保所有对象有足够的可用内存,且不定时,时间短(惰性)。通常在内存较少,新对象急需内存时发生。
可以使用静态方法System.GC.Collect()来建议GC立即执行。一般在废弃对象后获执行对GC造成的停滞敏感的程序部分。
除了内存之外,需要重视的是,对象也会占用非内存有限资源:文件、网络/数据库连接。GC是不会释放他们的(是啊,怎么可以等懒惰的GC来释放他们)。
在C++中,我们有析构函数。程序员可以在其中释放(内存/非内存)资源,而且一调用立即执行。(可别羡慕,虽然可操作空间大了,性能好了,但没有GC,还会有些坑:悬垂指针,内存泄漏)
Class ClassA(){
...
~ClassA(){
//释放资源。
}
}
C#也有析构函数,但只有GC能调用,所以不能像C++那样好用,不适合用来释放非内存有限资源了。
另外,GC会立即回收没有析构函数的废弃对象,但会把带析构函数的废弃对象先放入一个特殊列表。然后结束突发性废弃对象判断后,再启动一个进程执行列表中每个对象的析构函数。(可能因为要优先释放没析构函数的快的对象给急需内存的燃眉之急吧,然后在不干扰当前进程任务的情况下,再切换个进程解决多事的有析构函数的对象)。然后还要再放入一个即将回收对象列表中,再等GC下一次突发动作后,才被回收。
也就是总共要两次GC突发动作,才能回收带析构函数的对象。会滞后,又费时,还要特殊的处理过程。
Class ClassA(){
...
~ClassA(){
//释放资源。
}
//以下语法意义与以上同意义,即在C#中析构函数就是C#.NET运行时的终结器
protected override void Finalize(){
//释放资源。
}
}
所以要它何用?
1、释放只有一个对象可访问的非有限资源。
2、调查垃圾回收的进程。通过在析构函数中修改静态变量,可以看到GC执行了多少次(利用了GC回收带析构函数的对象的特殊机制)。(具体参考《C#Primer Plus中文版第1版》p396)
3、作为释放非托管资源的最终保险措施(下文会提到)
回到原先的问题,那该怎么释放对象占有的非内存有限资源呢?
微软鼓励程序员自己使用所谓的"Dispose设计方式",即自己写个Dispose函数去释放资源。而且为了避免意外没有调用Dispose去释放非托管资源,要在析构函数中调用Dispose函数。还有在Dispose函数最后用System.GC.SuppressFinalize()指示GC不要在调用过Dispose之后再调用析构函数(不能两次释放资源)。
在实际使用"Dispose"设计方式时,System提供了IDisposable接口。另外仅有需释放的非托管资源时(托管资源有GC帮忙啦,不需要上保险),才需要重写析构函数去调用Dispose。
具体实现和注意事项请参考以下文章
http://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html
有关GC一些重要的事实:
局部变量被设为null以早点被回收是没有意义的。
类的静态变量被创建后不会被回收,除非设为null。而在大系统里,可能有很多静态变量占内存,所以不再用时,要即时设为null。
参考
《C#Primer Plus中文版第1版》
http://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html
http://www.cnblogs.com/luminji/archive/2011/04/07/2007205.html