.NET垃圾收集关键方法解析

开发 后端
文章通过分析.NET垃圾收集中的重要方法,让大家更加了解.Net GC的运行机制,相信大家通过本文,能够很全面的了解.NET GC方面的知识。

之前51cto曾报道过关于.NET垃圾收集机制中的算法与代龄,说到.NET垃圾收集,就不得不提到其中的关键方法,其是实现.NET GC运行机制的前提。

1.Dispose()方法

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

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

使用using与try+finally的区别

可以说二者没有任何区别,因为using只是编辑器级的优化,它与try+finally有着相同的作用,以下是一段使用using的代码,它在IL阶段也是以try+finally呈现的,但是,using的优点是,在代码离开using块时,using会自动调用Idispose接口的Dispose()方法。

  1. public partial class _Default : System.Web.UI.Page  
  2. {      
  3. protected void Page_Load(object sender, EventArgs e)   
  4. {   
  5. using (DataSet ds = new DataSet())  
  6. {   
  7.  }  
  8. }  
  1. .method family hidebysig instance void  Page_Load(object sender,class [mscorlib]System.EventArgs e) cil managed  
  2. {  
  3.     // 代码大小       29 (0x1d)  
  4.     .maxstack  2  
  5.     .locals init ([0] class [System.Data]System.Data.DataSet ds,  
  6.              [1] bool CS$4$0000)  
  7.     IL_0000:  nop  
  8.     IL_0001:  newobj     instance void [System.Data]System.Data.DataSet::.ctor()  
  9.     IL_0006:  stloc.0  
  10.     .try  
  11.     {  
  12.       IL_0007:  nop  
  13.       IL_0008:  nop  
  14.       IL_0009:  leave.s    IL_001b  
  15.     }  // end .try  
  16.     finally  
  17.     {  
  18.       IL_000b:  ldloc.0  
  19.       IL_000c:  ldnull  
  20.       IL_000d:  ceq  
  21.       IL_000f:  stloc.1  
  22.       IL_0010:  ldloc.1  
  23.       IL_0011:  brtrue.s   IL_001a  
  24.       IL_0013:  ldloc.0  
  25.       IL_0014:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()  
  26.       IL_0019:  nop  
  27.       IL_001a:  endfinally  
  28.     }  // end handler  
  29.     IL_001b:  nop  
  30.     IL_001c:  ret  
  31. } // end of method _Default::Page_Load 

2. GC.Collect()方法

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

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

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

所以如果刚刚发生了一次自然的收集,垃圾对象就会非常之少,而此时程序又显式的进行了收集调用,那么自然, GC虽然小有收获,但是策略引擎就会认为:这很不值得,才收集了这么点垃圾,也许该减少GC的次数。这样一来,垃圾收集器努力保持的自然节奏就被打乱了,同时,对象类型的创建效率与频率,也会被“策略引擎”捕捉到,从而改变代的数量与容量。

所以,额外的调用GC,代价高昂,甚至会降低效率。显示的调用GC.Collect(),实质是在用“时间换空间”,而通常在程序设计中,我们推荐的设计原则是“空间换时间”,比如使用各种各样的缓存,也有例外,如果你掌握了整个应用程序的情况,明确的知道何时会产生大量垃圾,也是可以显示调用该方法的。综上,尽量不要显示调用GC.Collect(),因为服务器的CPU比内存要贵的多! #p#

3. 析构函数(Finalize())

我们知道,GC只负责释放托管资源,非托管资源GC是无法释放的。类似文件操作、数据库连接等都会产用非托管资源。Finalize方法是用于释放非托管资源的,等同于C#中是析构函数,C#编译器在编译构造函数时,会隐式的将析构函数编译为Finalize()对应的代码,并确定在finally块中执行了base.Finalize()。析构函数中只能释放非托管资源,而不要在任何托管资源进行析构,原因如下:

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

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

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

下面我们来重点说说第(2)点,为何包含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两次运行,终于回收了带有析构函数的对象。复活实例:

  1. private void Form1_Load(object sender, EventArgs e)   
  2. {  
  3. Resource re = new Resource();     
  4. re = null;GC.Collect();  
  5. GC.WaitForPendingFinalizers();  
  6. //***GC.Collect()没起作用哦。   
  7. label1.Text = re.num.ToString();  
  8. }    
  9. public class Resource  
  10. {  
  11. public int num;  
  12. ~Resource()  
  13. {  
  14.  
  15. }  
  16. }  

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

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

SuppressFinalize用于那些即有析构函数来释放资源,又实现了Dispose()方法释放资源的情况下:将GC.SuppressFinalize(this)添加至Dispose()方法中,以确保程序员调用Dispose()后,GC就不必再次收集了,即实现Idisposable中的Dispose()方法,又使用析构函数,一个双保险,大家不要迷惑,其实在释放非托管资源时,使用一个即可,推荐使用前者。代码如下:

  1. public class Resource : Idisposable  
  2. {  
  3.   private bool isDispose = false;  
  4.   //实现Dispose(),后面还有析构函数,以防程序员忘记调用Dispose()方法  
  5.     public void Dispose()   
  6.       {  
  7.        Dispose(true);  
  8.     GC.SuppressFinalize(this);  
  9.       }  
  10.    protected virtual void Dispose(bool disposing)  
  11.    {  
  12.     if (!isDispose)  
  13.     {  
  14.      if (disposing)  
  15.      {  
  16.       //清理托管资源  
  17.      }  
  18.      //清理非管资源  
  19.     }  
  20.     isDispose = true;  
  21.    }  
  22.       Resource ()  
  23.    {  
  24.     Dispose(false);  
  25.    }  
  26.  } 

4.弱引用(WeakReference)

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

  1. public class SomeObject   
  2. {  
  3.  
  4. }  
  5. public static void Main()   
  6. {  
  7.  SomeObject so = new SomeObject();  
  8.  WeakReference WRso = new WeakReference(so);  
  9.  so = null;  
  10.  Console.WriteLine(WRso.IsAlive); // True  
  11.  // 调用GC 手动回收。  
  12.  GC.Collect();  
  13.  Console.WriteLine(WRso.IsAlive); // False  

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

文章原标题:.NET Discovery系列之三--深入理解.NET垃圾收集机制(下)

原文链接:http://www.cnblogs.com/isline/archive/2009/03/04/1402713.html

【编辑推荐】

  1. .NET垃圾收集机制 了解算法与代龄
  2. 浅析提高.NET垃圾回收性能的几种方法
  3. 观点:微软.NET各子技术领域的应用前景
  4. .NET Framework 4.0功能特点详细讲解
  5. 深入.NET Framework 4.0 关于Lazy的点滴
责任编辑:王晓东 来源: CSDN博客
相关推荐

2010-01-06 16:33:50

.Net Framew

2010-02-22 08:58:35

JVM内存模型垃圾收集

2010-03-04 10:08:54

.Net垃圾收集

2009-10-30 10:47:48

VB.NET垃圾收集器

2009-06-15 16:14:40

Java垃圾收集算法GC

2024-03-27 10:27:35

延迟垃圾收集器

2024-01-15 11:12:28

Go内存开发

2009-09-18 09:16:06

.NET垃圾回收

2021-09-07 11:23:09

智能垃圾箱物联网IOT

2011-05-10 16:04:45

Java垃圾收集器

2011-08-17 15:37:23

Objective-C垃圾收集

2010-01-21 16:37:56

VB.NET变量声明

2017-09-21 14:40:06

jvm算法收集器

2024-03-15 08:04:30

G1CMSJVM

2022-07-25 10:15:29

垃圾收集器Java虚拟机

2011-07-21 14:54:26

java垃圾收集器

2017-05-09 15:34:54

方案垃圾收集信息化

2023-02-26 11:50:04

Hbase程序Oracle

2010-01-15 16:12:40

VB.NET调用DLL

2010-01-21 17:48:25

VB.NET Sing
点赞
收藏

51CTO技术栈公众号