l ** Fina ** ** li ** ** ze **
在上一篇文章中我分配使用资源一共五步,我们已经知道了 GC 是如何释放无用对象的内存了。但是它怎么实现第四步清空资源使用状态、释放利用到的一些非内存的系统资源呢? .NET 引入了 Fina li ze 来完成这个任务。
GC 在无用单元回收时一旦发现某个对象有 Fina li ze 方法,便调用它。所以我们的 Fina li ze 方法一定要尽量少做事情,以提高内存回收的速度。另外,在 Fina li ze 方法中不要有任何的线程同步等操作,以防止 GC 线程被挂起。
我们可以用两种方法来写自己的 Fina li ze 方法。一种就是显示的实现,如下面的代码:
代码 1
pub li c class SomeClass
{
pub li c SomeClass()
{
}
protected override void Fina li ze()
{
Console.WriteLine(“Fina li zing…”);
}
}
使用这种方法时要注意一点, .NET 不会帮你做调用基类的 Fina li ze 方法。如果需要调用基类的 Fina li ze 方法,需要显示的调用。如下面代码:
代码 2
pub li c class SomeClass
{
pub li c SomeClass()
{
}
protected override void Fina li ze()
{
Console.WriteLine(“Fina li zing…”);
base.Fina li ze(); // 调用基类 Fina li ze 方法
}
}
另外一种方法就是析构函数。 C# 中的析构函数不同于 C++ 。我们看下面的代码:
代码 3
pub li c class SomeClass
{
pub li c SomeClass()
{
}
~SomeClass()
{
Console.WriteLine(“Fina li zing…”);
}
}
它等同于代码 2 。
使用 Fina li ze 方法要特别小心。因为使用 Fina li ze 方法的对象要比普通的对象花时间; GC 也要花更多的时间来回收。而且 CLR 并不能保证调用 Fina li ze 的顺序,所以如果对象间有关联(比如一个成员变量先被 Fina li ze 了,如果在 Fina li ze 方法里还使用它,就会出错),就会更麻烦。
GC 是如何实现 Fina li ze 的呢? GC 维护了两个队列, Fina li zation 队列和 Freachable 队列。在托管堆分配对象的时候, GC 如果发现这个对象实现了一个 Fina li ze 方法,就把它加到 Fina li zation 队列。如图:
当托管堆的内存不足的时候, GC 开始对堆进行回收。 GC 回收一个对象前,先检查 Fina li zation 队列中是否有这个对象的指针,如果有,就将其放入 Freachable 队列。 Freachable 队列被认为是根( root )的一部分,所以 GC 不会对其作回收。 GC 第一次回收后,堆如下图:
对象 G 和对象 E 不在根的范围之内,被回收。对象 F 和对象 C 由于需要 Fina li ze 被放入到 Freachable 队列,这个队列被认为是根的一部分,所以这是对象 F 和对象 C 就复活了,没有被 GC 回收。 Freachable 队列中的对象的 Fina li ze 方法被一个特殊的线程执行。这个线程平时处于非活动状态,一旦 Freachable 队列不再为空,它就醒过来,一一执行这个队列中对象中的 Fina li ze 方法。执行过后如下图:
这时对象 F 和对象 C 不再是根的一部分,如果此时 GC 进行回收,将会被认作无用对象进行回收,回收后如下图:
上面简单描述了 Fina li ze 作用及其内部的工作原理。下面来说一下 Generation 。
l ** Generation **
每次都对整个对进行搜索,压缩是非常耗时的。微软总结了一些过去的开发中出现的现象,其中有一条就是,越是新的对象,越是最快被丢弃不再使用。微软根据这个经验在内存回收中引入了 Generation 的概念,我此处暂时将其翻译成代。托管堆开始的时候是空的,程序启动开始在其中分配对象,这时候的对象就是第 0 代 (Generation 0) 对象。如下图:
接下来,到托管堆空间不足, GC 进行了第一次回收,剩下的没有被回收的对象就升为第一代,之后再新分配的对象就是第 0 代(图甲)。再之后 GC 再进行回收的话只回收第 0 代,未被回收的第 0 代升级为第一代,原来的第一代升级为第 0 代(图乙)。
GC 缺省的代( Generation )最高就是 2 ,升级到第二代就不会再升级了。那什么时候 GC 回收第一,第二代呢?当 GC 回收完第 0 代后,发现内存空间还不够,就会回收第一代,回收完第一代,还不够,就回收第二代。
这一篇也写了不少了,所以下一篇再继续,下一篇写 WeakReference 和如何在自己的代码中控制 GC 的动作。