你清楚.Net的垃圾回收机制吗?

你能简述一下GC的工作原理吗?

怎么样才能有效的管理内存呢?

Using语句体内实例化的对象有什么作用?等等相关问题。

本节的组织如下:

1..Net的类型和内存分配

2.GC垃圾收集器的工作原理

3.什么是非托管资源

4.如何有效释放对象资源。

现在开始我们本节的学习。

 1..Net的类型和内存分配

Net中的所有类型都是(直接或间接)从System.Object类型派生的。

    CTS中的类型被分成两大类——引用类型(reference type,又叫托管类型[managed type]),分配在内存堆上,值类型(value type)。值类型分配在堆栈上。如图



201005201274357836234.gif

值类型在栈里,先进后出,值类型变量的生命有先后顺序,这个确保了值类型变量在推出作用域以前会释放资源。比引用类型更简单和高效。堆栈是从高地址往低地址分配内存。

引用类型分配在托管堆(Managed Heap)上,声明一个变量在栈上保存,当使用new创建对象时,会把对象的地址存储在这个变量里。托管堆相反,从低地址往高地址分配内存,如图

201005201274357874281.gif

2.GC垃圾收集器的工作原理

      上图中,当dataSet使用过期以后,我们不显示销毁对象,堆上的对象还继续存在,等待GC的 回收。

垃圾收集器通过分代支持对象的年龄化是推荐的但不是必需的。一代在内存里是一个具有相对年龄的对象的单位。对象的代号或年龄标识对象属于那个分代。在应用程序的生命周期里,越近创建的对象属于越新的代,并且比早创建的对象具有较低的分代号。最近分代里的对象代号是0.

      在new对象时,要先搜索空闲链表,找到最适合内存块,分配,调整内存块链表,合并碎片。new操作几乎可以在O(1)的时间完成,把堆顶指针加1。工作原理是: 当托管堆上剩余空间不足,或者Generator 0 的空间已满的时候GC运行,开始回收内存。垃圾回收的开始,GC对堆内存的压缩调整,对象集中到顶部。GC在扫描垃圾的时候会占用一定的CPU时间片的,最初的GC算法真的是扫描整个堆,效率低。现在的GC把堆中的对象分成3代,最进入堆的是第0代(generation 0), 其次是generation 1, generation2. 第一次GC只扫描第0代。如果回收的空间足够当前使用就不必扫描其它generation的对象。所以,GC创建对象的效率比C++高效,不需要扫描全部堆空间。它通过扫描策略,再加上内存管理策略带来的性能提升,足以补偿GC所占用的CPU时间。

 3.什么是非托管资源

  常见的非托管资源就是包装操作系统资源的对象,例如文件,窗口或网络连接,对于这类资源虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它知道如何清理这些资源。好在.net Framework提供的Finalize()方法,它允许在垃圾回收器回收该类资源前,适当的清理非托管资源。这里列举几种常见的非托管资源:画笔、流对象、组件对象等等资源(Object,OdbcDataReader,OleDBDataReader,Pen,Regex,Socket,StreamWriter,ApplicationContext,Brush,Component,ComponentDesigner,Container,Context,Cursor,FileStream,Font,Icon,Image,Matrix,Timer,Tooltip)。(参考MSDN)

4.如何有效释放非托管资源。

     GC无法管理非托管资源,那么如何释放非托管资源呢?.Net提供了两种方式:

(1)析构函数:垃圾收集器回收非托管对象的资源时,会调用对象的终结方法Finalize(),进行资源的清理工作,但是由于GC工作规则的限制,GC调用对象的Finalize方法,第一次不会释放资源,第二次调用之后才删除对象。

(2)继承IDisposable接口,实现Dispose()方法,IDisposable接口定义了一个模式(具有语言级的支持),为释放未托管的资源提供了确定的机制,并避免产生析构函数固有的与垃圾收集器相关的问题。

   为了更好的理解垃圾回收机制,我特地写了部分代码,里面添加了详细的注释。定义单个类FrankClassWithDispose(继承接口IDisposableFrankClassNoFinalize(没终结器)FrankClassWithDestructor(定义了析构函数)。

具体代码如下: