对.Net 垃圾回收Finalize 和Dispose的理解

来源:互联网 发布:java list泛型转换 编辑:程序博客网 时间:2024/05/22 13:41

引用地址:http://liuzhiwei.cnblogs.com/archive/2006/06/28/437662.html



我们先来谈谈析构函数。


析构函数是不可继承的。因此,除了自已所声明的析构函数外,一个类不具有其他析构函数。

由于析构函数要求不能带有参数,因此它不能被重载,所以一个类至多只能有一个析构函数。

析构函数是自动调用的,它不能被显式调用。当任何代码都不再可能使用一个实例时,该实例就符合被销毁的条件。

此后,它所对应的实例析构函数随时均可能被调用。销毁一个实例时,按照从派生程度最大到派生程度最小的顺序,调用该实例的继承链中的各个析构函数。析构函数可以在任何线程上执行。


下列示例的输出

using System;class A{    ~A(){Console.WriteLine("A's destructor");}}class B: A{    ~B() {Console.WriteLine("B's destructor");}}class Test{    static void Main() {        B b = new B();        b = null;        GC.Collect();        GC.WaitForPendingFinalizers();    }}

显示:
B's destructor
A's destructor


这是由于继承链中的析构函数是按照从派生程度最大到派生程度最小的顺序调用的。

析构函数实际上是重写了 System.Object 中的虚方法 Finalize。C# 程序中不允许重写此方法或直接调用它(或它的重写)。例如,下列程序

class A{    override protected void Finalize() {}   // error    public void F() {        this.Finalize();                     // error    }}

包含两个错误。

编译器的行为就像此方法和它的重写根本不存在一样。因此,以下程序:

class A{     void Finalize() {}                     // permitted}

是有效的,所声明的方法隐藏了 System.Object 的 Finalize 方法。







好,现在我们开始来谈谈Finalize 和Dispose。



Finalize 和Dispose(bool disposing)和 Dispose() 的相同点:

                                       这三者都是为了释放非托管资源服务的.


Finalize 和 Dispose() 和Dispose(bool disposing)的不同点:

                Finalize是CLR提供的一个机制, 它保证如果一个类实现了Finalize方法,那么当该类对象被垃圾回收时,垃圾回收器会调用Finalize方法.而该类的开发者就必须在Finalize方法中处理 非托管资源的释放. 但是什么时候会调用Finalize由垃圾回收器决定,该类对象的使用者(客户)无法控制.从而无法及时释放掉宝贵的非托管资源.由于非托管资源是比较宝贵了,所以这样会降低性能.


                Dispose(bool disposing)不是CRL提供的一个机制, 而仅仅是一个设计模式(作为一个IDisposable接口的方法),它的目的是让供类对象的使用者(客户)在使用完类对象后,可以及时手动调用非托管资源的释放,无需等到该类对象被垃圾回收那个时间点.这样类的开发者就只需把原先写在Finalize的释放非托管资源的代码,移植到Dispose(bool disposing)中.  而在Finalize中只要简单的调用 "Dispose(false)"(为什么传递false后面解释)就可以了.


      这个时候我们可能比较疑惑,为什么还需要一个Dispose()方法?难道只有一个Dispose(bool disposing)或者只有一个Dispose()不可以吗? 


答案是:  
        只有一个Dispose()不可以. 为什么呢?因为如果只有一个Dispose()而没有Dispose(bool disposing)方法.那么在处理实现非托管资源释放的代码中无法判断该方法是客户调用的还是垃圾回收器通过Finalize调用的.无法实现判断如果是客户手动调用,那么就不希望垃圾回收器再调用Finalize()(调用GC.SupperFinalize方法).另一个可能的原因(:我们知道如果是垃圾回收器通过Finalize调用的,那么在释放代码中我们可能还会引用其他一些托管对象,而此时这些托管对象可能已经被垃圾回收了, 这样会导致无法预知的执行结果(千万不要在Finalize中引用其他的托管对象). 



        所以确实需要一个bool disposing参数, 但是如果只有一个Dispose(bool disposing),那么对于客户来说,就有一个很滑稽要求,Dispose(false)已经被Finalize使用了,必须要求客户以Dispose(true)方式调用,但是谁又能保证客户不会以Dispose(false)方式调用呢?所以这里采用了一中设计模式:重载  把Dispose(bool disposing)实现为 protected, 而Dispose()实现为Public,那么这样就保证了客户只能调用Dispose()(内部调用Dispose(true)//说明是客户的直接调用),客户无法调用Dispose(bool disposing). 


范例如下:

public class BaseResource: IDisposable {     //前面我们说了析构函数实际上是重写了 System.Object 中的虚方法 Finalize,     //默认情况下,一个类是没有析构函数的,也就是说,对象被垃圾回收时不会被调用Finalize方法     ~BaseResource() {         // 为了保持代码的可读性性和可维护性,千万不要在这里写释放非托管资源的代码         // 必须以Dispose(false)方式调用,以false告诉Dispose(bool disposing)函数是从垃圾回收器在调用Finalize时调用的         Dispose(false);     }           // 无法被客户直接调用     // 如果 disposing 是 true, 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源都可以释放     // 如果 disposing 是 false, 那么函数是从垃圾回收器在调用Finalize时调用的,此时不应当引用其他托管对象所以,只能释放非托管资源     protected virtual void Dispose(bool disposing) {          // 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源都可以释放          if(disposing) {              OtherManagedObject.Dispose(); // 释放 托管资源          }                  DoUnManagedObjectDispose();  //释放非托管资源                           // 那么这个方法是被客户直接调用的,告诉垃圾回收器从Finalization队列中清除自己,从而阻止垃圾回收器调用Finalize方法.                  if(disposing) GC.SuppressFinalize(this);                  }        //可以被客户直接调用    public void Dispose(){       Dispose(true); //必须以Dispose(true)方式调用,以true告诉Dispose(bool disposing)函数是被客户直接调用的        } }


上面的范例达到的目的: 

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

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






===================================================================================


java finalize的一些问题
引用地址:http://blog.sina.com.cn/s/blog_66a6172c01018jda.html




一、finalize的作用
     由于在java中存在垃圾回收器,因此许多人认为它相当于C++中的析构函数,然而,垃圾回收器只会回收使用new方法获得的内存,而通过其他方法得到的内存以及任何其他系统资源(比如文件句柄、数据库连接等)都不能通过垃圾回收器得到释放。因此我们需要其它的释放函数。那么能够做到这一切的是finalize()方法(但是,一定不要使用finalzie方法去释放这些资源)。finalize方法的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。因此,如果有一些需要释放的资源,可以讲释放语句写在finalize方法中,它们将在垃圾回收器工作时自动得到执行。那么,为什么不能使用该方法去释放资源呢?


二、为什么尽量避免使用finalize函数
  首先,finalize()一定会被执行,但是并不总是那么及时,原因有二:
  1、垃圾回收器并不总是工作,只有当内存资源告急时,垃圾回收器才会工作;
  2、即使垃圾回收器工作,finalize方法也不一定得到执行,这是由于程序中的其他线程的优先级远远高于执行finalize()函数线程的优先级。
  

因此,当finalize还没有被执行时,系统的其他资源,比如文件句柄、数据库连接池等已经消耗殆尽,造成系统崩溃。
  第二,如果一种未被捕获的异常在使用finalize方法时被抛出,这个异常不会被捕获,finalize方法的终结过程也会终止,造成对象出于破坏的状态。
  第三,第二种情况已经是一种特例,还有其他的情况也会造成对象不会被销毁,从而浪费内存。
  第四,垃圾回收和finalize方法的执行本身就是对系统资源的消耗,有可能造成程序的暂时停止,因此在程序中尽量避免使用finalize方法。


三、finalize函数的应用场景
  1、当对象的使用者忘记显示释放使用资源的时候,finalize函数充当安全网的角色,相当于最后一道防火墙(祈祷finalize方法能够被及时执行);
  2、释放本地方法申请的非关键资源(程序中嵌入的其他语言申请的非关键资源,关键资源仍然要显示释放);
  3、终结条件(java编程思想88页)
  PS:记得在重载的finalize方法中最后调用super.finalize();系统不会自动调用


四、为什么不能显示直接调用finalize方法
  如前文所述,finalize方法在垃圾回收时一定会被执行,而如果在此之前显示执行的话,也就是说finalize会被执行两次以上,而在第一次资源已经被释放,那么在第二次释放资源时系统一定会报错,因此一般finalize方法的访问权限和父类保持一致,为protected。