对于托管资源和非托管资源的理解

对于托管资源和非托管资源的理解

 

      在.net 编程环境中,系统的资源分为托管资源和非托管资源。html

  对于托管的资源的回收工做,是不须要人工干预回收的,并且你也没法干预他们的回收,所可以作的只是了解.net CLR如何作这些操做。也就是说对于您的应用程序建立的大多数对象,能够依靠 .NET Framework 的垃圾回收器隐式地执行全部必要的内存管理任务。
    
    资源分为两种,托管的内存资源,这是不须要咱们操心的,系统已经为咱们进行管理了;那么对于非托管的资源,这里再重申一下,就是Stream,数据库的链接,GDI+的相关对象,还有Com对象等等这些资源,须要咱们手动去释放。程序员

  对于非托管资源,您在应用程序中使用完这些非托管资源以后,必须显示的释放他们,例如System.IO.StreamReader的一个文件对象,必须显示的调用对象的Close()方法关闭它,不然会占用系统的内存和资源,并且可能会出现意想不到的错误。数据库

  我想说到这里,必定要清楚什么是托管资源,什么是非托管资源了?释放。编程

  最多见的一类非托管资源就是包装操做系统资源的对象,例如文件,窗口或网络链接,对于这类资源虽然垃圾回收器能够跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。还好.net Framework提供了Finalize()方法,它容许在垃圾回收器回收该类资源时,适当的清理非托管资源。若是在MSDN Library 中搜索Finalize将会发现不少相似的主题,这里列举几种常见的非托管资源:ApplicationContext,Brush,Component,ComponentDesigner,Container,安全

Context,Cursor,FileStream,Font,Icon,Image,Matrix,Object,OdbcDataReader,OleDBDataReader,Pen,Regex,Socket,StreamWriter,Timer,Tooltip 等等资源。可能在使用的时候不少都没有注意到!网络

    关于托管资源,就不用说了撒,像简单的int,string,float,DateTime等等,.net中超过80%的资源都是托管资源。app

非托管资源如何释放,.NET Framework 提供 Object.Finalize 方法,它容许对象在垃圾回收器回收该对象使用的内存时适当清理其非托管资源。默认状况下,Finalize 方法不执行任何操做。默认状况下,Finalize 方法不执行任何操做。若是您要让垃圾回收器在回收对象的内存以前对对象执行清理操做,您必须在类中重写 Finalize 方法。然而你们均可以发如今实际的编程中根本没法override方法Finalize(),在C#中,能够经过析构函数自动生成 Finalize 方法和对基类的Finalize 方法的调用。ide

例如:函数

复制代码
~MyClass()
{
  // Perform some cleanup operations here.
}
  该代码隐式翻译为下面的代码。
protected override void Finalize()
{
  try
  {
    // Perform some cleanup operations here.
  }
  finally
  {
    base.Finalize();
  }
}
复制代码

可是,在编程中,并不建议进行override方法Finalize(),由于,实现 Finalize 方法或析构函数对性能可能会有负面影响。一个简单的理由以下:用 Finalize 方法回收对象使用的内存须要至少两次垃圾回收,当垃圾回收器回收时,它只回收没有终结器(Finalize方法)的不可访问的内存,这时他不能回收具备终结器(Finalize方法)的不能够访问的内存post

。它改成将这些对象的项从终止队列中移除并将他们放置在标记为“准备终止”的对象列表中,该列表中的项指向托管堆中准备被调用其终止代码的对象,下次垃圾回收器进行回收时,就回收并释放了这些内存。

C#托管及未托管对象管理

 C#中的对象分为值类型和引用类型,两者最大的区别在于数据的存储方式和存储位置. WINDOWS操做系统使用虚拟寻址系统来管理程序运行时产生的数据存放.简单的说,该系统管理着一个内存区域,在该区域中划拨出一部分出来专门存放值类型变量,称为堆栈,堆栈采用先进后出的原则,将值类型变量从区域的最高地址位开始向低位地址存储,先进后出,后进先出的管理方式保证了值类型变量在出了做用域后能即便的清除占用的内存区域,因为堆栈速度快,所保存的数据通常不太大,这部分通常不须要用户专门操做. 值类型保存在堆栈汇总,堆栈有很是高的性能,但对于全部的变量来讲仍是不太灵活。一般咱们但愿使用一个方法分配内存,来存储一些数据,并在方法退出后的很长一段时间内数据还是可使用的。只要是用new运算符来请求存储空间,就存在这种可能性——例如全部的引用类型。此时就要使用托管堆。它在垃圾收集器的控制下工做,托管堆(或简称为堆)是系统管理的大内存区域中的另外一个内存区域。要了解堆的工做原理和如何为引用数据类型分配内存,看看下面的代码:
Customer arabel = new Customer();
这行代码完成了如下操做:首先,分配堆上的内存,以存储Customer实例(一个真正的实例,不仅是一个地址)。而后把变量arabel的值设置为分配给新Customer对象的内存地址(它还调用合适的Customer()构造函数初始化类实例中的字段,但咱们没必要担忧这部分)。
Customer实例没有放在堆栈中,而是放在内存的堆中。若是咱们这样操做:
Customer newaddress = arabel ;
这时候,newaddress也会保存在堆栈中,其值和arabel 相同,都是存储Customer实例的堆地址.
     知道了这些,咱们会发现这样一个问题,若是堆栈中arabel 和newaddress两个变量过时销毁,那堆中保存的Customer对象会怎样?实际上它仍保留在堆中,一直到程序中止,或垃圾收集器删除它为止. C#的垃圾收集器若是没有显示调用,会定时运行并检查内存,删除没有任何变量引用的数据.看起来彷佛不错,可是想一想,垃圾回收器并非时时检查,它是定时运行,而在这段时间内若是产生大量的过时数据驻留在内存中..... 那么或许咱们能够经过调用System.GC.Collect(),强迫垃圾收集器在代码的某个地方运行,System.GC是一个表示垃圾收集器的.NET基类, Collect()方法则调用垃圾收集器。可是,这种方式适用的场合不多,(难道销毁一个对象就让垃圾回收检查一便内存吗?)例如,代码中有大量的对象刚刚中止引用,就适合调用垃圾收集器。何况垃圾收集器的逻辑不能保证在一次垃圾收集过程当中,从堆中删除全部过时数据,对于不受垃圾回收器管理的未托管对象(例如文件句柄、网络链接和数据库链接),它是无能为力的。那该怎么作呢?
  这时须要制定专门的规则,确保未托管的资源在回收类的一个实例时释放。
在定义一个类时,可使用两种机制来自动释放未托管的资源。这些机制经常放在一块儿实现,由于每一个机制都为问题提供了略为不一样的解决方法。这两个机制是:
●         声明一个析构函数,做为类的一个成员
●         在类中实现System.IDisposable接口
下面依次讨论这两个机制,而后介绍如何同时实现它们,以得到最佳的效果。
析构函数
前面介绍了构造函数能够指定必须在建立类的实例时进行的某些操做,在垃圾收集器删除对象时,也能够调用析构函数。因为执行这个操做,因此析构函数初看起来彷佛是放置释放未托管资源、执行通常清理操做的代码的最佳地方。可是,事情并非如此简单。因为垃圾回首器的运行规则决定了,不能在析构函数中放置须要在某一时刻运行的代码,若是对象占用了宝贵而重要的资源,应尽量快地释放这些资源,此时就不能等待垃圾收集器来释放了.
IDisposable接口
一个推荐替代析构函数的方式是使用System.IDisposable接口。IDisposable接口定义了一个模式(具备语言级的支持),为释放未托管的资源提供了肯定的机制,并避免产生析构函数固有的与垃圾函数器相关的问题。IDisposable接口声明了一个方法Dispose(),它不带参数,返回void,Myclass的方法Dispose()的执行代码以下:

复制代码
class Myclass : IDisposable
{
    public void Dispose()
    {
       // implementation
    }
}
复制代码

Dispose()的执行代码显式释放由对象直接使用的全部未托管资源,并在全部实现IDisposable接口的封装对象上调用Dispose()。这样,Dispose()方法在释放未托管资源时提供了精确的控制。
假定有一个类ResourceGobbler,它使用某些外部资源,且执行IDisposable接口。若是要实例化这个类的实例,使用它,而后释放它,就可使用下面的代码:

ResourceGobbler theInstance = new ResourceGobbler();

    // 这里是theInstance 对象的使用过程

theInstance.Dispose();

若是在处理过程当中出现异常,这段代码就没有释放theInstance使用的资源,因此应使用try块,编写下面的代码:

复制代码
ResourceGobbler theInstance = null;
try
{
    theInstance = new ResourceGobbler();
//   这里是theInstance 对象的使用过程
}
finally  
{
   if (theInstance != null) theInstance.Dispose();
}
复制代码

即便在处理过程当中出现了异常,这个版本也能够确保老是在theInstance上调用Dispose(),老是释放由theInstance使用的资源。可是,若是老是要重复这样的结构,代码就很容易被混淆。C#提供了一种语法,能够确保在引用超出做用域时,在对象上自动调用Dispose()(但不是 Close())。该语法使用了using关键字来完成这一工做—— 但目前,在彻底不一样的环境下,它与命名空间没有关系。下面的代码生成与try块相对应的IL代码:

using (ResourceGobbler theInstance = new ResourceGobbler())
{
    //   这里是theInstance 对象的使用过程
}

using语句的后面是一对圆括号,其中是引用变量的声明和实例化,该语句使变量放在随附的复合语句中。另外,在变量超出做用域时,即便出现异常,也会自动调用其Dispose()方法。若是已经使用try块来捕获其余异常,就会比较清晰,若是避免使用using语句,仅在已有的try块的finally 子句中调用Dispose(),还能够避免进行额外的缩进。
注意:
对于某些类来讲,使用Close()要比Dispose()更富有逻辑性,例如,在处理文件或数据库链接时,就是这样。在这些状况下,经常实现 IDisposable接口,再执行一个独立的Close()方法,来调用Dispose()。这种方法在类的使用上比较清晰,还支持C#提供的 using语句。

前面的章节讨论了类所使用的释放未托管资源的两种方式:
●         利用运行库强制执行的析构函数,但析构函数的执行是不肯定的,并且,因为垃圾收集器的工做方式,它会给运行库增长不可接受的系统开销。
●         IDisposable接口提供了一种机制,容许类的用户控制释放资源的时间,但须要确保执行Dispose()。
通常状况下,最好的方法是执行这两种机制,得到这两种机制的优势,克服其缺点。假定大多数程序员都能正确调用Dispose(),实现IDisposable接口,同时把析构函数做为一种安全的机制,以防没有调用Dispose()。下面是一个双重实现的例子:

复制代码
public class ResourceHolder : IDisposable
{
     private bool isDispose = false;

   // Pointer to an external unmanaged resource.
   private IntPtr handle;

   // Other managed resource this class uses.
   private Component Components;
      
      // 显示调用的Dispose方法
  public void Dispose()
      {
           Dispose(true);
          GC.SuppressFinalize(this);
       }

        // 实际的清除方法
  protected virtual void Dispose(bool disposing)
       {
            if (!isDisposed)
          {
               if (disposing)
           {
                     // 这里执行清除托管对象的操做.
                  }
                  // 这里执行清除非托管对象的操做
               CloseHandle(handle);
               handle = IntPtr.Zero; 
            }
    
        isDisposed=true;
      }

       // 析构函数
      ~ResourceHolder()
      {
            Dispose (false);
      }
}
复制代码

能够看出,Dispose()有第二个protected重载方法,它带一个bool参数,这是真正完成清理工做的方法。Dispose(bool)由析构函数和IDisposable.Dispose()调用。这个方式的重点是确保全部的清理代码都放在一个地方。
传递给Dispose(bool)的参数表示Dispose(bool)是由析构函数调用,仍是由IDisposable.Dispose()调用——Dispose(bool)不该从代码的其余地方调用,其缘由是:
●         若是客户调用IDisposable.Dispose(),该客户就指定应清理全部与该对象相关的资源,包括托管和非托管的资源。
●         若是调用了析构函数,在原则上,全部的资源仍须要清理。可是在这种状况下,析构函数必须由垃圾收集器调用,并且不该访问其余托管的对象,由于咱们再也不能肯定它们的状态了。在这种状况下,最好清理已知的未托管资源,但愿引用的托管对象还有析构函数,执行本身的清理过程。
isDispose成员变量表示对象是否已被删除,并容许确保很少次删除成员变量。这个简单的方法不是线程安全的,须要调用者确保在同一时刻只有一个线程调用方法。要求客户进行同步是一个合理的假定,在整个.NET类库中反复使用了这个假定(例如在集合类中)。最后,IDisposable.Dispose()包含一个对System.GC. SuppressFinalize()方法的调用。SuppressFinalize()方法则告诉垃圾收集器有一个类再也不须要调用其析构函数了。由于 Dispose()已经完成了全部须要的清理工做,因此析构函数不须要作任何工做。调用SuppressFinalize()就意味着垃圾收集器认为这个对象根本没有析构函数.

  正确理解以上内容,能够大大优化系统性能,及时释放不须要的数据,不能仅靠C#提供的自动回收机制,也须要程序员使用更灵活的办法!两者合一既能让程序运行飞快,也让系统更加稳定!

相关文章
相关标签/搜索