C#中Dispose,finalize,GC,析构函数区别

释放类所使用的未托管资源的两种方式: 程序员

       1.利用运行库强制执行的析构函数,但析构函数的执行是不肯定的,并且,因为垃圾收集器的工做方式,它会给运行库增长不可接受的系统开销。 数据库

       2.IDisposable接口提供了一种机制,容许类的用户控制释放资源的时间,但须要确保执行Dispose()。数组

       通常状况下,最好的方法是执行这两种机制,得到这两种机制的优势,克服其缺点。假定大多数程序员都能正确调用Dispose(),实现IDisposable接口,同时把析构函数做为一种安全的机制,以防没有调用Dispose()。缓存

ispose()有第二个protected重载方法,它带一个bool参数,这是真正完成清理工做的方法。Dispose(bool)由析构函数和IDisposable.Dispose()调用。这个方式的重点是确保全部的清理代码都放在一个地方。  安全

传递给Dispose(bool)的参数表示Dispose(bool)是由析构函数调用,仍是由IDisposable.Dispose()调用——Dispose(bool)不该从代码的其余地方调用,其缘由是: 函数

●  若是客户调用IDisposable.Dispose(),该客户就指定应清理全部与该对象相关的资源,包括托管和非托管的资源。 性能

●  若是调用了析构函数,在原则上,全部的资源仍须要清理。可是在这种状况下,析构函数必须由垃圾收集器调用,并且不该访问其余托管的对象,由于咱们再也不能肯定它们的状态了。在这种状况下,最好清理已知的未托管资源,但愿引用的托管对象还有析构函数,执行本身的清理过程。 this

 isDispose成员变量表示对象是否已被删除,并容许确保很少次删除成员变量。这个简单的方法不是线程安全的,须要调用者确保在同一时刻只有一个线程调用方法。要求客户进行同步是一个合理的假定,在整个.NET类库中反复使用了这个假定(例如在集合类中)。spa

 最后,IDisposable.Dispose()包含一个对System.GC.   SuppressFinalize()方法的调用。SuppressFinalize()方法则告诉垃圾收集器有一个类再也不须要调用其析构函数了。由于Dispose()已经完成了全部须要的清理工做,因此析构函数不须要作任何工做。调用SuppressFinalize()就意味着垃圾收集器认为这个对象根本没有析构函数 .net

[csharp] view plain copy

  1. public class ResourceHolder : IDisposable   
  2. {   
  3.         private bool isDispose = false;   
  4.         public void Dispose()  
  5.         {  
  6.                 Dispose(true);  
  7.                 GC.SuppressFinalize(this);  
  8.         }  
  9.         protected virtual void Dispose(bool disposing)     
  10.         {   
  11.                 if (!isDisposed)   
  12.                 {  
  13.                         if (disposing)     
  14.                         {  
  15.                                 //释放托管资源   
  16.                         }  
  17.                         //释放非托管资源   
  18.                 }  
  19.                 isDisposed=true;  
  20.       }   
  21.       ~ResourceHolder()  
  22.       {  
  23.                 Dispose(false);   
  24.       }   
  25. }   

1.被分配内存空间的对象最有可能被释放。在方法执行时,就须要为该方法的对象分配内存空间,搜索最近分配的对象集合有助于花费最少的代价来尽量多地释放内存空间。

2.生命期最长的对象释放的可能性最小,通过几轮垃圾回收后,对象仍然存在,搜索它时就须要进行大量的工做,却只能释放很小的一部分空间。

3.同时被分配内存的对象一般是同时使用,将它们彼此相连有助于提升缓存性能和回收效率。

C#中的回收器是分代的垃圾回收器(Gererational Garbage Collector) 它将分配的对象分为3个类别或代。(可用GC.GetGeneration方法返回任意做为参数的对象当前所处的代)最近被分配内存的对象被放置于第0 代,由于第0代很小,小到足以放进处理器的二级(L2)缓存,因此它可以提供对对象的快速存取。通过一轮垃圾回收后,仍然保留在第0代中的对象被移进第1 代中,再通过一轮垃圾内存回收后,仍然保留在第1代中的对象则被移进第2代中,第2代中包含了生存期较长的对象。(类比于JAVA中的分代收集器)

在C#中值类型是在堆栈中分配内存,它们有自身的生命周期,因此不用对它们进行管理,会自动分配和释放。而引用类型是在堆中分配内存的。因此它的分配和释放就须要像回收机制来管理。C#为一个对象分配内存时,托管堆能够当即返回新对象所需的内存,由于托管堆相似于简单的字节数组,有一个指向第一个可用内存空间的指针,指针像游标同样向后移动,一段段内存就分配给了正在运行的程序的对象。在不须要太多垃圾回收的程序中,托管堆性能优于传统的堆。

当第0代中没有能够分配的有效内存时,就触发了第0代中的一轮垃圾回收,它将删除那些再也不被引用的对象,并将当前正在使用的对象移至第1代。而当第0代垃圾回收后依然不能请求到充足的内存时,就启动第1代垃圾回收。若是对各代都进行了垃圾回收后仍没有可用的内存就会引起一个 OutOfMemoryException异常。

 

终结器(Finalize方法)

在有些状况下,类能够提供一个终结器在对象被销毁时执行,终结器是一个名为Finalize的受保护的方法:

protected void Finalize()

{

base.Finalize();

//释放外部资源

}

垃圾回收器使用名为“终止队列”的内部结构跟踪具备 Finalize 方法的对象。每次您的应用程序建立具备 Finalize 方法的对象时,垃圾回收器都在终止队列中放置一个指向该对象的项。托管堆中全部须要在垃圾回收器回收其内存以前调用它们的终止代码的对象都在终止队列中含有项。(实现Finalize方法或析构函数对性能可能会有负面影响,所以应避免没必要要地使用它们。用Finalize方法回收对象使用的内存须要至少两次垃圾回收。当垃圾回收器执行回收时,它只回收没有终结器的不可访问对象的内存。这时,它不能回收具备终结器的不可访问对象。它改成将这些对象的项从终止队列中移除并将它们放置在标为准备终止的对象列表中。该列表中的项指向托管堆中准备被调用其终止代码的对象。垃圾回收器为此列表中的对象调用Finalize方法,而后将这些项从列表中移除。后来的垃圾回收将肯定终止的对象确实是垃圾,由于标为准备终止对象的列表中的项再也不指向它们。在后来的垃圾回收中,实际上回收了对象的内存。归纳而言,就是将垃圾回收分为了三个阶段,第一个阶段回收没有Finalize方法或者析构函数的对象,第二个阶段调用那些析构函数或者Finalize方法,第三个阶段回收那些刚调用了析构函数或者Finalize方法的对象。)

终结器(finalizer)是在回收过程当中,由垃圾回收器调用的方法。如何含有终结器的对象到了G2中,那么就会须要很是长的时间来回收。事实上,根据应用程序运行时间的长短,对象颇有机会直到应用程序退出以前都不会被回收(特别是其中包含的重要的资源得不得释放,将会对性能产生很大的影响,好比说数据库链接得不到释放。)

 

Dispose方法

在不使用终结器时,能够考虑使用Dispose方法,你可使用这个方法来释放所保存包括的在托管对象引用在内的任何资源。系统类中如何实现了Dispose方法,那么通常Dispose方法中都包含了SuppressFinalize方法,这个方法会告知系统这个类已经再也不须要析构了,这样能够提升释放资源的效率。因此在自定义类中的Dispose方法应该调用GC.SuppressFinalize来告知运行时这些对象不须要析构。以下所示:

public void Dispose(){

object.Dispose();

dbConnection.Dispose();

GC.SuppressFinalize(this);//申明不须要终结

}

建立并使用了Dispose方法的对象,就须要使用完该对象以后调用这些方法,最好是在Finally中调用。

 

System.GC

GC类包含了可以使用户与垃圾回收机制进行互操做的静态方法,包括发起新一轮垃圾回收操做的方法。肯定某对象当前所在代的方法及当前分配内存空间的方法。

GC.Collect();//无参时将发起一轮全面的回收。(彻底回收以前,应用程序会中止响应,所以不建议使用。)

GC.Collect(i);//(0<=i<=2)对第i代进行垃圾回收。

GetTotalMemory将返因分配于托管堆上的内存空间总量。当参数为True时,在计算以前将进行一轮全面的垃圾回收。以下所示:

long totalMemory = System.GC.GetTotalMemory(True);

下面是 在.NET Framework 2.0 版中是新增的公共方法:

通知运行库在安排垃圾回收时应考虑分配大量的非托管内存

public static void AddMemoryPressure (long bytesAllocated)//bytesAllocated已分配的非托管内存的增量。

返回已经对对象的指定代进行的垃圾回收次数。

public static int CollectionCount (int generation)

通知运行库已释放非托管内存,在安排垃圾回收时不须要再考虑它。

public static void RemoveMemoryPressure (long bytesAllocated)

C# 中的析构函数其实是重写了 System.Object 中的虚方法 Finalize

  三种最常的方法以下:

  1. 析构函数;(由GC调用,不肯定何时会调用)

  2. 继承IDisposable接口,实现Dispose方法;(能够手动调用。好比数据库的链接,SqlConnection.Dispose(),由于若是及时释放会影响数据库性能。这时候会用到这个,再如:文件的打开,若是不释放会影响其它操做,如删除操做。调用Dispose后这个对象就不能再用了,就等着被GC回收。)

  3. 提供Close方法。(相似Dispose可是,当调用完Close方法后,能够经过Open从新打开)

析构函数不能显示调用,而对于后两种方法来讲,都须要进行显示调用才能被执行。而Close与Dispose这两种方法的区别在于,调用完了对象的Close方法后,此对象有可能被从新进行使用;而Dispose方法来讲,此对象所占有的资源须要被标记为无用了,也就是此对象要被销毁,不能再被使用。

 

析构函数

Dispose方法

Close方法

意义

销毁对象

销毁对象

关闭对象资源

调用方式

不能被显示调用,在GC回收是被调用

须要显示调用

或者经过using语句

须要显示调用

调用时机

不肯定

肯定,在显示调用或者离开using程序块

肯定,在显示调用时

   下面提供一个模式来结合上面的 析构函数和Dispose方法。

[csharp] view plain copy

  1. public class BaseResource: IDisposable   
  2.   
  3.   { //前面咱们说了析构函数其实是重写了 System.Object 中的虚方法 Finalize, 默认状况下,一个类是没有析构函数的,也就是说,对象被垃圾回收时不会被调用Finalize方法   
  4.   
  5.   ~BaseResource()   
  6.   
  7.   { // 为了保持代码的可读性性和可维护性,千万不要在这里写释放非托管资源的代码   
  8.   
  9.   // 必须以Dispose(false)方式调用,以false告诉Dispose(bool disposing)函数是从垃圾回收器在调用Finalize时调用的   
  10.   
  11.   Dispose(false);   
  12.   
  13.   }   
  14.   
  15.   // 没法被客户直接调用   
  16.   
  17.   // 若是 disposing 是 true, 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源均可以释放   
  18.   
  19.   // 若是 disposing 是 false, 那么函数是从垃圾回收器在调用Finalize时调用的,此时不该当引用其余托管对象因此,只能释放非托管资源   
  20.   
  21.   protected virtual void Dispose(bool disposing)   
  22.   
  23.   {   
  24.   
  25.   // 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源均可以释放   
  26.   
  27.   if(disposing)   
  28.   
  29.   {   
  30.   
  31.   // 释放 托管资源   
  32.   
  33.   OtherManagedObject.Dispose();   
  34.   
  35.   }   
  36.   
  37.   //释放非托管资源   
  38.   
  39.   DoUnManagedObjectDispose();   
  40.   
  41.   // 那么这个方法是被客户直接调用的,告诉垃圾回收器从Finalization队列中清除本身,从而阻止垃圾回收器调用Finalize方法.   
  42.   
  43.   if(disposing)   
  44.   
  45.   GC.SuppressFinalize(this);   
  46.   
  47.   }   
  48.   
  49.   //能够被客户直接调用   
  50.   
  51.   public void Dispose()   
  52.   
  53.   {   
  54.   
  55.   //必须以Dispose(true)方式调用,以true告诉Dispose(bool disposing)函数是被客户直接调用的   
  56.   
  57.   Dispose(true);   
  58.   
  59.   }   
  60.   
  61.   } 

上面的范例达到的目的:

  1/ 若是客户没有调用Dispose(),未能及时释放托管和非托管资源,那么在垃圾回收时,还有机会执行Finalize(),释放非托管资源,可是形成了非托管资源的未及时释放的空闲浪费

  2/ 若是客户调用了Dispose(),就能及时释放了托管和非托管资源,那么该对象被垃圾回收时,不会执行Finalize(),提升了非托管资源的使用效率并提高了系统性能

  最后:

  若是类中使用了非托管资源,则要考虑提供Close方法,和Open方法。并在您的Dispose方法中先调用 Close方法。

  在使用已经有类时,如SqlConnection。若是暂时不用这个链接,能够考虑用Close()方法。若是不用了就考虑调用Dispose()方法。

相关文章
相关标签/搜索