转自 http://blog.csdn.net/zhuweisky/article/details/415665
从C++转向C#的程序员经常被C#中的析构函数、Dispose方法、Close方法和Finalize方法搞混,再一看到Finalize队列、Freachable队列就更不知所云了。
是的,C++中没有这么多麻烦的东东,C++中只有一个析构函数,通常这个析构函数什么事都不做,但是如果你在析构函数所属类的构造函数或其它成员函数中申请或分配了资源而没有释放的话,那么一定要记得在析构函数中释放这些资源,否则就会造成资源泄漏――这是C++程序中的最大隐患之一。上面叙述的是C++中析构函数的主要用途,当然还有很多其它的用处,比如在析构函数中递减计数,或将析构函数声明为private可以限制继承和限制在栈中生成该析构函数所属类的对象等等。
再看C#,C#一下子搞来这么多和析构函数相关的概念,它们的区别究竟是什么?为什么需要这些容易混淆的东西?且听我慢慢道来。
一.与析构函数相关的方法
同C++一样,C#中的析构函数主要也是用来释放申请的资源的,但是它只需要释放非托管资源,因为托管资源会由垃圾回收器GC负责处理。注意,GC对窗口句柄、打开的文件和流等非托管资源是一无所知的。
1.析构函数与Finalize方法
在C++中,析构函数由“~类名”标志,在C#中也可以这样,如
class A { ~A() { Console.WriteLine("A's destructor"); } }
但是在C#中,析构函数会被编译器扩展成为重写的 System.Object 的Finalize 方法,Finalize方法相当于析构函数的别名,只不过不允许在代码中直接出现Finalize方法的定义或对其的调用。如
class A { protected override void Finalize() // error ! { Console.WriteLine("A's destructor"); } }
这样看来,我们可以将析构函数和Finalize方法看作同一个东西,只不过析构函数是我们显式书写的,而Finalize方法则是析构函数经编译器转化之后得到的,它是隐式的。如果不是为了更清楚的了解垃圾收集的过程,我们程序员也许并不需要去知道Finalize方法的存在。然而如果一个.NET程序员不了解垃圾收集技术,我相信他不会是一个好的.NET程序员。先在这里提一下,一个类如果声明了析构函数,那么在该类的实例被GC清除前将会自动异步调用。当然,如果一个类没有声明析构函数,便不会产生对应的Finalize方法,那么当该类的某个实例的引用计数变为0时,会在接下来的一轮垃圾收集中直接清除掉,而不需要等到再下一轮垃圾收集,所以没有析构函数的类的实例在回收时效率比对应的有析构函数的类的实例要高得多。关于这方面的进一步描述会在后文中出现。
2.Dispose(Close)方法
Dispose(Close)方法和析构函数的功能是一样的,他们的代码几乎完全相同。而且Dispose方法和Close方法仅仅是互为别名。
但是,Close(Dispose)方法是被用户显示调用的,如果一个类中除了Close(Dispose)方法外还定义析构函数,那么在Close(Dispose)方法结束时,应调用GC.SuppressFinalize()方法,以告诉GC到时清除对象内存时不需要在调用Finalize方法了。对对象调用Close(Dispose)方法后,对象仍然存在,直到GC将它回收,程序员无法直接精确地控制托管对象何时消亡。
3.Finalize方法(析构函数)与Dispose方法的区别
最重要的区别是Finalize方法(即析构函数)是由GC自动调用的,而Dispose方法是由程序员直接调用的。
照理说只要Finalize方法就足够了,为什么还搞个Dispose方法出来搅水了?答案在于,由程序员调用Dispose方法可以在合适的时候及时释放对象持有的各种非托管资源。因为我们无法控制GC何时销毁我们不再使用的对象,我们也就无法控制GC何时调用我们不再使用对象的Finalize方法,而我们又需要及时的释放对象持有的重要资源,这时我们就可以通过调用Dispose方法来做到。
Finalize 方法在Dispose(Close)方法未被调用的情况下充当防护措施来清理非托管资源。
二.Finalization队列与Freachable队列
讲到Finalize队列和Freachable队列就不得不提垃圾回收技术。
.NET中采用的垃圾回收技术是基于Mark Sweep算法的。Mark Sweep算法分为Mark(标记“活”的对象)和Sweep(清除)两个阶段,并且为了减少碎片,还加入了Compact阶段。关于垃圾回收的更详细的介绍请参见2003年10月份《开发高手》上的《理解.NET CLR垃圾回收技术》一文。
1. Finalization队列
在托管堆分配对象(即用new)的时候,GC如果发现这个对象实现了一个Finalize方法,就把它加到Finalization队列(即在Finalization队列中添加一个引用或指针指向它)。
//假设类EXC实现了Finalize方法 (C#)
EXC D = new EXC() ;
执行上述语句后,Finalization队列如图1。
2.Freachable队列
当托管堆的内存不足的时候,GC开始对堆进行回收。当GC检测到一个对象的引用计数为0,接着G检查Finalization队列中是否有这个对象的指针,如果有,就将其放入Freachable队列(即在Freachable队列中添加一个引用或指针指向它),否则,就直接清除这个对象并释放其内存。
图1 Finalization队列示例
在图1中,我们可知对象A会被放入到Freachable队列,而对象C的内存会被GC直接清除。如下图所示
图2 Freachable队列示例
Freachable队列中的对象的Finalize方法会被一个特殊的线程执行。这个线程平时处于非活动状态,在一定的时刻它醒过来,当发现Freachable队列不为空时,就一一执行这个队列中的对象的Finalize方法。执行过后该对象就和没有Finalize方法的垃圾对象一样了,即图2中的A对象的析构函数被执行后,在Finalization队列中不会再有引用指向它了,如下图所示
图3 A对象的Finalize方法被执行后的Finalization队列
现在A对象和那些没有Finalize方法的垃圾对象一样,将在下一轮GC启动时被直接清除。
在看完上面的介绍后,相信你对C#中的析构函数、Dispose方法、Close方法和Finalize方法、Finalize队列、Freachable队列这些概念以及它们的作用已经有了一个基本的了解了。当然,在叙述这些概念的时,为了更清晰地描述目标,我忽略很多相关的东西,如垃圾回收时的分代技术,还有强引用、弱引用等等,如果您需要进一步了解相关内容,可以查阅相关资料,关于这些内容的资料是很丰富的。