zoukankan      html  css  js  c++  java
  • CLR回收非托管资源

    一.非托管资源

          在《垃圾回收算法之引用计数算法》、《垃圾回收算法之引用跟踪算法》和《垃圾回收算法之引用跟踪算法》这3篇文章中,我们介绍了垃圾回收的一些基本概念和原理,但需要说明的是:这些文章中,介绍的都是对托管资源的回收,所谓托管资源,直白一点,你可以理解为托管堆上分配的对象,它由GC来自动管理。

          但本节,我们要介绍另外一种资源——非托管资源,它不是分配在托管堆上的资源,而是诸如文件、网络连接、网络套接字Socket、Windows互斥内核对象等其他的资源。

          C#程序有时也需要使用非托管资源,如我们使用FileStream打开文件句柄,并使用句柄操作文件,这里的文件就是非托管资源,当我们使用完FileStream时,GC会在某个时间点回收FileStream,但文件不是托管资源,GC对它一无所知,这样会造成内存的泄漏。为了应对这种情况,CLR提供了一种终结(Finalize)机制,以帮助程序释放非托管资源。

    二.终结原理

     1.Finalize方法

       在语法上,C#Finalize方法非常类似于C++中的析构器,在类名前添加~符号来定义Finalize方法,CLRFinalize方法生成名为Finalizeprotected overvide方法,方法体被try..finally方法块包裹,finally中调用了base.Finalize方法.

      如下所示:  

    public class TestClass
    {
       ~TestClass()
      {
      }
    }

     生所IL代码如下:

     

           Finalize机制允许CLR在判断对象为垃圾之后,但在回收垃圾之前执行一些代码,即执行Finalize方法,比如你可以在这些代码里回收非托管资源,下一步,CLR就可以回收托管堆上的资源(托管资源)了.

          注意,并不是说你只能在Finalize方法中回收非托管资源,只是一种习惯性做法。

          实际上,非托管资源也是一定要先于托管资源回收的。这是为什么呢?假设一个对象被判断为垃圾, 由于CLR对于非托管资源一无所知,CLR先回收了托管资源,如果在Finalize方法内部需要访问托管资源,则会造成内存泄漏,相反,非托管资源先释放掉,那么剩下的托管资源由于真正的不可达(既没有被非托管资源访问也没有被托管资源访问),就可以被GC垃圾回收了。

          那一个对象的托管资源和非托管资源可不可以一起回收呢?答案也是不行的.因为,CLR采用一个特殊的、高优先级的专用线程调用Finalize方法(这样做是为了避免潜在的线程同步问题,使用应用程序的普通优先级线程就有可能发生空上问题),无法保证一起回收,它甚至不保证多个Finalize方法的调用顺序。

          在接下来介绍的Finalize内部工作原理时,我们会介绍到freachable队列,特殊线程就是监控该队列的数据,freachable队列为空时,线程将睡眠,但一旦队列中有记录项出现时,线程就会被唤醒,将每一项从freachable队列中移除,同时调用每个对象的Finalize方法。

    2.Fianlize的内部工作原理

        我们来通过《CLR via C#》中的例子来说明Finalize的内部工作原理,在这之前我们要说明两个概念:

        a.终结列表:用来存储实现了Finalize方法的对象指针列表,注意,CLR认为,如果你是从System.Object中继承了Finalize方法,则不会认为你是终结对象,但如果你重写了Finalize方法,则CLR认为对        象是终结对象,则会将它加入终结列表。那在程序运行的时候,对象何时加入终结列表呢?《CLR via C#》中说,在应用程序创建新对象时,该类型的实例构造函数被调用之前。

        b.freachable:全称是Finalization Reachable List,它存储着所有被判断为垃圾的终结对象,等待着CLR专用线程对它的调用。

           明白了以上的概念,现在我们来图解Finalize的内部工作原理。

        如图所示,在初始状态下,在G0中,A C E F是可达的,C D I实现了Finalize方法,被加入了终结列表(即我们上面所说的概念a),freachable队列为空:

        

        现在,GC开始扫描所有的根,形成对象可达图,注意GC会发现B D G H I J均为垃圾(即同步块索引中的一位标志为0),同时发现D和I虽然为垃圾,但是它是终结对象,因此将它们放入freachable列表中(上面所说的概念b),因为Finalize在被CLR专用线程调用时,这个对象必然要是存活的,所以使得freachable的这些终结对象(D和I)“复活”,同时它们引用的对象(J)也复活了。经过这两个步骤,GC形成了对象可达图,如下所示:

        

        下面开始进行GC回收垃圾工作,在清除垃圾后,B G H被清掉,剩下的对象被压缩并提升至G1代中。随后,特殊的进程清空freachable队列,执行每个对象(这里是D和I)的Finalize方法:

        

       执行完freachable的Finalize方法后,D I J现在没有任何对象引用它们,它们将在下次的GC组成对象可达图时,变得不可达(即垃圾)。在第二次(也可能是某一次)GC时,D I J被清除掉。

        

       这里需要说明

       a:终结对象的清除需要两次垃圾回收才能释放它们占用的垃圾;

       b:这两次垃圾回收有可能不是连续的,因为GC执行第一次垃圾回收的,终结对象被提升至下一代,而在进行下一代的垃圾回收之前,前一代很有可能进行了1次或多次的垃圾回收。

       

    3.Finalize方法的缺陷

        a.因为可终结对象在调用时必须存活,造成可终结对象要经过两次释放才能真正释放掉资源,并在GC中提升至下一代,其引用的对象也会被提升,使对象活得比正常时间长,这增大了内存消耗;

        b.Finalize方法的执行时间和执行顺序是控制不了的,因为只有GC完成后才会运行Finalize,而只有应用程序请求更多的内存而不够时才会进行GC;

        c.根据b点说明,我们不可以在一个终结对象的Fialize方法中调用另一个终结对象,因为Finalize方法的执行顺序控制不了,我们无法保证调用时另一个终结对象还存在,但可以安全地访问值类型的类型;

        d.CLR用专用线程调用Finalize方法来避免死锁,如果Finalize方法阻塞,则特殊线程也会发生阻塞,无法调用更多的Finalize方法,这使得GC永远回收不了终结对象占用的内存,内存则会一直泄漏;同时如果Finalize未处理的异常则会造成进程终止,无法捕捉该异常。

    三.Dispose模式

          前面我们介绍了Finalize的缺陷,其中c点,我们可以在程序中控制Finalize不要访问另外一个终结对象,对于d点,我们可以在Finalize中进行异常控制;对于a和b点,我们就不能控制了,这会带来一个致命的问题,如果我的非托管资源很少,在应对高并发的请求时,GC又不知道何时执行,非托管资源又在GC之后,对于非托管资源的释放成为性能的瓶颈,比如Socket等。

          微软提供了一个Dispose模式来解决这个问题,它让我们能够显式地释放非托管资源,控制非托管资源的生存期。

          实现了IDisposable接口,就实现了dispose模式。

    1. Dispose模式的设计原则
    2. a.可以重复调用Dispose方法
    1. b.析构函数应该Dispose带参方法来释放非托管资源
    2. c.Dispose方法应该可以释放托管资源和非托管资源
    3. d.Dispose方法应该调用GC.SuppressFinalize()方法,指示垃圾回收器不再重复回收该对象

    • e.CLR为继承了IDisposable接口的类提供了特殊的语法糖,使用using(MyDispose myOjb=new MyDispoe()){ … },它会在跳出using的区域时调用MyDispose的Dispose方法。

          微软在官方网站上提供了Dispose模式的案例,如下所示

    using System;
    
    class BaseClass : IDisposable
    {
       // 标志位:标志Dispose方法是否被调用过
       bool disposed = false;
    
       // 实现IDisposable接口
       public void Dispose()
       { 
          Dispose(true);
          GC.SuppressFinalize(this);           
       }
    
       // True时:释放托管和非托管资源,手工调用
    //False时:只释放非托管资源,CLR专用线程调用
       protected virtual void Dispose(bool disposing)
       {
          if (disposed)
             return; 
    
          if (disposing) {
             // 释放托管资源
          }
    
          // 释放所有的非托管资源
          disposed = true;
       }
    
       ~BaseClass()
       {
          Dispose(false);
       }
    }

    2.源码学习:看下.Net Framework中FileStrem的Dispose模式

    a.FileStreamr的基类Stream实现了Dispose模式

    public abstract class Stream : IDisposable {
        public void Dispose()
    { 
    //通过Close方法释放托管资源和非托管资源,同时通知GC
          Close();
        }
    public virtual void Close()
        { 
           Dispose(true);
           GC.SuppressFinalize(this);
         }
        //虚方法,留给FileSteam去实现
        protected virtual void Dispose(bool disposing)
    {
        
    }
    }

    b.FileStream类,实现了Finalize方法,并重写了Dispose带参方法,当然FileStream的方法实现了很多功能,写法也较复杂,我们这里只需要了解一下关注的Dispose模式即可。

    public class FileStream : Stream{
        ~FileStream(){
           if (_handle != null) {
               Dispose(false);//调用基类的Dispose方法,释放非托管资源
           }
    }
    protected override void Dispose(bool disposing)
         {
            try {
               if (_handle != null && !_handle.IsClosed) { 
                  if (_writePos > 0) {
                    FlushWrite(!disposing);//在这里释放资源
                  }
               }
           }
           finally {
              if (_handle != null && !_handle.IsClosed){
                _handle.Dispose(); 
                _canRead = false;
                _canWrite = false;
                _canSeek = false; 
                base.Dispose(disposing);
              }
      }
    }

    参考文档

    1.《CLR via C#》(第4版)

    2. https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx

    3. https://www.zhihu.com/question/46462047Philip Chan的回答

    4. http://blog.csdn.net/qing101/article/details/52484987

    5. https://www.zhihu.com/question/29265003

    6.《.Net最佳实践》

  • 相关阅读:
    30+简约时尚的Macbook贴花
    20+非常棒的Photoshop卡通设计教程
    20+WordPress手机主题和插件【好收藏推荐】
    75+精美的网格网站设计欣赏
    TopFreeTheme精选免费模板【20130629】
    45个有新意的Photoshop教程和技巧
    30个高质量的旅游网站设计
    55个高质量的Magento主题,助你构建电子商务站点
    一个弹框引起的彻夜加班
    开始跟踪Redis啦,开帖
  • 原文地址:https://www.cnblogs.com/gudi/p/6505024.html
Copyright © 2011-2022 走看看