zoukankan      html  css  js  c++  java
  • .Net Discovery系列之四 深入理解.Net垃圾收集机制(下)

    上一节给大家介绍了 .Net GC的运行机制,下面来讲下与GC相关的重要方法。

    第二节.GC关键方法解析

    1.Dispose()方法

    Dispose可用于释放所有资源,包括托管的和非托管的,需要自己实现。

    大多数的非托管资源都要求手动释放,我们应当为释放非托管资源公开一个方法,实现释放非托管资源的方法有很多种,实现IDispose接口的Dispose方法是最好的,这可以给使用你类库的程序员以明确的说明,让他们知道怎样释放你的资源;而且C#中用到的using语句快,也是在离开语句块时自动调用Dispose方法。

    这里需要注意的是,如果基类实现了IDispose接口,那么它的派生类也必须实现自己的IDispose,并在其Dispose方法中调用基类中Dispose方法。只有这样的才能保证当你使用派生类实例后,释放资源时,连同基类中的非托管资源一起释放掉。

    插曲:使用using与try+finally的区别

    可以说2者没有任何区别,因为using只是编辑器级的优化,它与try+finally有着相同的作用,以下是一段使用using的代码,它在IL阶段也是以try+finally呈现的:

    C#:

    public partial class _Default : System.Web.UI.Page
        {
         protected void Page_Load(object sender, EventArgs e) 
         {
           using (DataSet ds = new DataSet())
           {
            }
         }
       }

       MSIL:
       .method family hidebysig instance void  Page_Load(object sender,class [mscorlib]System.EventArgs e) cil managed
       {
        // 代码大小       29 (0x1d)
        .maxstack  2
        .locals init ([0] class [System.Data]System.Data.DataSet ds,
                 [1] bool CS$4$0000)
        IL_0000:  nop
        IL_0001:  newobj     instance void [System.Data]System.Data.DataSet::.ctor()
        IL_0006:  stloc.0
        .try
        {
          IL_0007:  nop
          IL_0008:  nop
          IL_0009:  leave.s    IL_001b
        }  // end .try
        finally
        {
          IL_000b:  ldloc.0
          IL_000c:  ldnull
          IL_000d:  ceq
          IL_000f:  stloc.1
          IL_0010:  ldloc.1
          IL_0011:  brtrue.s   IL_001a
          IL_0013:  ldloc.0
          IL_0014:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
          IL_0019:  nop
          IL_001a:  endfinally
        }  // end handler
        IL_001b:  nop
        IL_001c:  ret
       } // end of method _Default::Page_Load

    但是,using的优点是,在代码离开using块时,using会自动调用Idispose接口的Dispose()方法。

    2. GC.Collect()方法

    如果我们在程序中显式的调用了垃圾收集器的collect接口,那么垃圾收集器会立即运行,完成内存对象的标记、压缩与清除工作,使用GC.Collect(i)还可以指定回收的代,然而aicken并不赞成各位同学显式调用它:

    ⑴. GC.Collect()做的并不只是回收内存,就像第一节中介绍的,在回收了内存之后,GC会重新整理内存,修正对象指针,让空闲内存连续,供CLR顺序分配内存,提高新建对象的效率。内存压缩整理工作非常耗用计算资源。

    ⑵.很少有人会关心到GC除了在内存吃紧以及资源空闲时运行,还会在什么时候运行。 其实GC的运行时机,还要受到一个叫做“策略引擎”的部件控制,它会观察GC的收集频率、效率等等。它会根据GC回收效果,调整GC运行的频率:即当某次GC回收效果颇丰时,它便会增加GC运行的频率,反之亦然。

    所以如果刚刚发生了一次自然的收集,垃圾对象就会非常之少,而此时程序又显式的进行了收集调用,那么自然, GC虽然小有收获,但是策略引擎就会认为:这很不值得,才收集了这么点垃圾,也许该减少GC的次数。这样一来,垃圾收集器努力保持的自然节奏就被打乱了。

    同时,对象类型的创建效率与频率,也会被“策略引擎”捕捉到,从而改变代的数量与容量。

    所以,额外的调用GC,代价高昂,甚至会降低效率。显示的调用GC.Collect(),实质是在用“时间换空间”,而通常在程序设计中,我们推荐的设计原则是“空间换时间”,比如使用各种各样的缓存。

    也有例外,如果你掌握了整个应用程序的情况,明确的知道何时会产生大量垃圾,也是可以显示调用该方法的。

    综上,尽量不要显示调用GC.Collect(),因为服务器的CPU比内存要贵的多!

    3. 析构函数(Finalize())

    我们知道,GC只负责释放托管资源,非托管资源GC是无法释放的。类似文件操作、数据库连接等都会产用非托管资源。

    Finalize方法是用于释放非托管资源的,等同于C#中是析构函数,C#编译器在编译构造函数时,会隐式的将析构函数编译为Finalize()对应的代码,并确定在finally块中执行了base.Finalize()。

    析构函数中只能释放非托管资源,而不要在任何托管资源进行析构,原因如下:

    ⑴你无法预测析构函数的运行时机,它不是按顺序执行的。当析构函数被执行的时候,也许你进行操作的托管资源已经被释放了。

    ⑵包含Finalize()的对象,需要GC的两次处理才能删除。

    ⑶CLR会在单独的线程上执行所有对象的Finalize()方法,无疑,如果频繁的Finalize(),会降低系统的性能。

    下面我们来重点说说第⑵点,为何包含Finalize()的对象,需要两次GC才能被清除。

    首先要了解与Finalize相关的两个队列:终止队列(Finalization Queue)与可达队列(Freachable Queue),这两个队列存储了一组指向对象的指针。

    当程序中在托管堆上分配空间时(new),如果该类含有析构函数,GC将在Finalization Queue中添加一个指向该对象的指针。

    在GC首次运行时,会在已经被确认为垃圾的对象中遍历,如果某个垃圾对象的指针被Finalization Queue包含,GC将这个对象从垃圾中分离出来,将它的指针储存到Freachable Queue中,并在Finalization Queue删除这个对象的指针记录,这时该对象就不是垃圾了——这个过程被称为是对象的复生(Resurrection)。当Freachable Queue一旦被添加了指针之后,它就会去执行对象的Finalize()方法,清除对象占用的资源。

    当GC再次运行时,便会再次发现这个含有Finalize()方法的垃圾对象,但此时它在Finalization Queue中已经没有记录了(GC首次运行时删掉了它的Finalization Queue记录),那么这个对象就会被回收了。

    至此,通过GC两次运行,终于回收了带有析构函数的对象。

    复活实例:

    private void Form1_Load(object sender, EventArgs e) 
       {
         Resource re = new Resource();
         re = null;GC.Collect();
         GC.WaitForPendingFinalizers();
         //首次GC.Collect()没起作用哦。 
         label1.Text = re.num.ToString();
       }
       public class Resource
      {
       public int num;
         ~Resource()
         {
           。。。
         }
       }

    看了上面的代码,大家应该了解什么是复活了吧!那么为什么要复生呢?因为首次GC时,这个对象的Finalize()方法还没有被执行,如果不经过复生就被GC掉,那么就连它的Finalize()一起回收了,Finalize()就无法运行了,所以必须先复生,以执行它的Finalize(),然后再回收。

    还有两个方法ReRegisterForFinalize和SuppressFinalize需要讲一讲,ReRegisterForFinalize是将指向对象的指针重新添加到Finalization Queue中(即召唤系统执行Finalize()方法),SuppressFinalize是将对象的指针从Finalization Queue中移除(即拒绝系统执行Finalize()方法)。

    SuppressFinalize用于那些即有析构函数来释放资源,又实现了Dispose()方法释放资源的情况下:将GC.SuppressFinalize(this)添加至Dispose()方法中,以确保程序员调用Dispose()后,GC就不必再次收集了,例如以下代码:

    public class Resource : Idisposable
       {
          private bool isDispose = false;
          //实现Dispose(),后面还有析构函数,以防程序员忘记调用Dispose()方法
          public void Dispose() {
          Dispose(true);
           GC.SuppressFinalize(this);
        }
       protected virtual void Dispose(bool disposing)
       {
        if (!isDispose)
        {
         if (disposing)
         {
          //清理托管资源
         }
         //清理非管资源
        }
        isDispose = true;
       }
       ~ Resource ()
       {
        Dispose(false);
       }
      }

    即实现Idisposable中的Dispose()方法,又使用析构函数,一个双保险,大家不要迷惑,其实在释放非托管资源时,使用一个即可,推荐使用前者。

    4.弱引用(WeakReference)

    最后一个话题:弱引用。在编程中,对于那些大对象建议使用这种引用方式,这种引用不影响GC回收:我们用过了某个对象,然后将其至null,这样GC就可以快速回收它了,但是没过多久我们又需要这个对象了,没办法,只好重新创建实例,这样就浪费了创建实例所需的计算资源;而如果不至null,就会浪费内存资源。对于这种情况,我们可以创建一个这个大对象的弱引用,这样在内存不够时GC可以快速回收,而在没有被GC回收前我们还可以再次利用该对象。

    public class SomeObject 
       {
         。。。
       }

       public static void Main() 
       {
          SomeObject so = new SomeObject();
          WeakReference WRso = new WeakReference(so);
         so = null;
          Console.WriteLine(WRso.IsAlive); // True
          // 调用GC 手动回收。
          GC.Collect();
          Console.WriteLine(WRso.IsAlive); // False

       }

    看到没,在so = null;后,它的弱引用依然是可用的。所以对于大对象的使用,aicken建议使用此种方式。另外,弱引用有长短之分:长弱引用在对象终结后,依然追踪对象;短弱引用则反之,aicken不建议人为干预GC的工作成果,所以推荐使用短弱引用,即上面代码中的方式。

    通过以上的讲解,相信大家已经能够很全面的了解.Net GC方面的知识了。

    转自:http://www.cnblogs.com/isline/archive/2009/03/04/1402713.html

  • 相关阅读:
    VIJOS-P1340 拯救ice-cream(广搜+优先级队列)
    uva 11754 Code Feat
    uva11426 GCD Extreme(II)
    uvalive 4119 Always an Interger
    POJ 1442 Black Box 优先队列
    2014上海网络赛 HDU 5053 the Sum of Cube
    uvalive 4795 Paperweight
    uvalive 4589 Asteroids
    uvalive 4973 Ardenia
    DP——数字游戏
  • 原文地址:https://www.cnblogs.com/zjoch/p/5237455.html
Copyright © 2011-2022 走看看