《Programming .Net Components》学习笔记(八)

.NET对象不会知道它们何时会成为垃圾,当托管堆被压缩时成为垃圾的对象会直接被覆写。这样就带来一个问题:如果对象持有比较大的消耗的资源(如文件,连接,通讯端口,数据结构,同步处理等等),那么对象应如何处理和释放这些资源那?为了解决这些问题,.Net提供了“object finalization(对象终结)”。如何对象有特殊的清理要做,那么它可以实现Finalize()的方法定义:

    public static class GC

    {

        public static void Collect();

        /* Other methods and members */

    }

当垃圾收集器判定一个对象为垃圾时,它会检查对象的元数据。如果对象实现了Finalize()方法,垃圾收集器就不会销毁这个对象。而是把它标记为“可达对象”(所以对象不会通过压缩托管堆的方式被覆写),然后移动对象从它的原始图表中到一个称为“finalization queue(终结队列)”的特殊队列中。这个队列实质上仅仅是另一个对象图表,队列的根保持着对象的可达性。垃圾收集器接下来继续执行收集垃圾和压缩托管堆。期间,一个分离的线程将迭代终结队列中的所有对象,调用每个对象上的Finalize()方法,让每个对象进行清理工作。Finalize()方法执行完毕后,垃圾收集器会从队列中移除对象。

 

一、显式垃圾收集

可以使用定义在System命名空间中的GC类中的Collect()方法显式触发垃圾收集:

    public static class GC

    {

        public static void Collect();

        /* Other methods and members */

    }

但是,最好不要以任何方式显式进行垃圾收集。垃圾收集是一个高消耗的操作,包括对象图表扫描,线程上下文转换(thread context switches),线程的挂起和恢复,潜在地磁盘访问,以及广泛的使用反射机制来读取对象元数据。启动垃圾收集机制的原因经常是因为想要调用Finalize()方法来清理对象持有的资源。可以使用确定性终结来代替来垃圾收集机制实现清理功能(后面会讲到)。


也可以使用HandleCollector辅助类来触发垃圾回收机制,.NET 2.0中的System.Runtime.InteropServices命名空间中:

    public sealed class HandleCollector

    {

        public HandleCollector(string name, int initialThreshold, int maximumThreshold);

        public HandleCollector(string name, int initialThreshold);

 

        public void Add();

        public void Remove();

        public int InitialThreshold { get;}

        public int MaximumThreshold { get;}

        public string Name{get:}

    }

HandleCollector允许保存高消耗的非托管资源的内存地址,如Windows或文件处理。为托管的每种资源类型使用一个HandleCollector对象。HandleCollector用来处理这样的对象——在内存总量中消耗较少的,但持有着高消耗的非托管处理.无论何时通过HandleCollector分配一个新的非托管资源监视,可以调用Add()方法。当要在Finalize()方法中取消资源分配,可以调用Remove()方法。在内部,HandleCollector维护着一个计数器,它的增减基于Add()和Remove()的调用。可见实际上HandleCollector的功能就是每一种处理类型的单纯的引用计数器。当构造一个新的HandleCollector对象时,指定initialmaximum thresholds。无论多少资源分配,只要在初始限定之内,就不会有任何垃圾回收实现。如果请求Add()方法并且资源计数超过了初始限定(但仍在最大限定之内),垃圾回收机制或许会发生,还基于一个自校正试探(a self-tuning heuristic)。如果请求Add()并且资源计数超过最大限定,那么垃圾回收机制总是会启动。

但是,使用HandleCollector会带来几个棘手的问题:

1.   怎样来定义限定的值?这些值可能在用户环境和同一用户超时之间发生变化。

2.   组件如何得知在同一计算机哪些其他应用程序是做相同处理的?

3.   如果触发了垃圾回收机制并且HandleCollector对象维护的处理不是垃圾,那么将会结束垃圾回收的支出但从中得不到任何好处。

综上所述,使用HandleCollector是一个不成熟的优化技术,应避免使用它。

 

二、Finalize()方法实现

在实现Finalize()方法时,需注意以下几点:

1.        当实现Finalize()时,注意同时也请求基类的Finalize()方法,来让基类执行它的清理:

        protected override void Finalize()

        {

            /* Object cleanup here */

            base.Finalize();

        }

典型的.NET类型System.Object包含了一个没有任何实现的受保护Finalize()方法,但无论基类是否确切的提供了Finalize()方法,都要请求它。

2.        确保定义Finalize()为一个protected方法,避免把Finalize()定义为private方法,因为这样可以预防从子类中调用Finalize()而发生错误。有趣的是,.NET通过反射机制来调用Finalize()方法,而不受访问限制的影响。。。

3.        避免中止请求,因为这将阻碍在队列中所有其他对象的终极操作,直到中止请求返回。

4.        终结必须不依赖于“线程亲和”来执行清理。线程亲和是组件设计的假设,假设组件实例总是运行于同一线程中。但是,Finalize()会在单独的垃圾回收线程中被请求,而不会在任何用户线程中。因而将不能访问任何在该特殊线程中的资源,如线程本地存储或线程相关静态变量。

5.        终结必须不依赖于特定的顺序(如对象A的资源释放必须在对象B之后)。两个对象应该可以以任意顺序添加到队列中去。

6.        即使在面对异常时请求基类的Finalize()实现也是非常必要的,可以通过try/finally机制来处理:

        protected override void Finalize()

        {

            try

            {

                /* Object cleanup here */

            }

            finally

            {

                base.Finalize();

            }

        }

基于上面几点足够适用于每一个类,C#编译器已经内建支持生成模板Finalize()代码。在C#中可以不提供Finalize()方法,而是提供一个C#析构器。编译器会把析构器转换定义为Finalize()方法,其中包含了异常处理机制和对基类Finalize()方法的请求:

    public class MyClass

    {

        public MyClass() { }

        ~MyClass()

        {

            //destructor code

        }

    }

下面是编译器实际生成的代码:

    public class MyClass

    {

        public MyClass() { }

        protected override void Finalize()

        {

            try

            {

                //destructor code

            }

            finally

            {

                base.Finalize();

            }

        }

    }

如果试图同时定义析构器和Finalize()方法,则编译器会产生一个编译错误。或者显式请求基类的Finalize()方法也会得到一个错误。如在类层次中的每个类都具有析构器,编译器生成的代码会按顺序请求每一个析构器,从最底层子类到最高层基类。


根据原版英文翻译总结的,所以不足和错误之处请大家不吝指正,谢谢:)

你可能感兴趣的:(programming)