今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个《WCF分布式开发必备知识》系列后的一次休息吧。以前被别人面试的时候问过我GC工作原理的问题,我现在面试新人的时候偶尔也会问相关的问题。那么你是否也遇到这样的问题呢?比如你清楚.Net的垃圾回收机制吗?你能简述一下GC的工作原理吗?怎么样才能有效的管理内存呢?Using语句体内实例化的对象有什么作用?等等相关问题。下面我们就来详细讨论一下。相信你看完以后也可以面试别人。
本节的组织如下,1..Net的类型和内存分配2.GC垃圾收集器的工作原理3.什么是非托管资源4.如何有效释放对象资源。总结.现在开始我们本节的学习。
1..Net的类型和内存分配
Net中的所有类型都是(直接或间接)从System.Object类型派生的。
CTS中的类型被分成两大类——引用类型(reference type,又叫托管类型[managed type]),分配在内存堆上,值类型(value type)。值类型分配在堆栈上。如图
值类型在栈里,先进后出,值类型变量的生命有先后顺序,这个确保了值类型变量在推出作用域以前会释放资源。比引用类型更简单和高效。堆栈是从高地址往低地址分配内存。
引用类型分配在托管堆(Managed Heap)上,声明一个变量在栈上保存,当使用new创建对象时,会把对象的地址存储在这个变量里。托管堆相反,从低地址往高地址分配内存,如图
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(继承接口IDisposable)、FrankClassNoFinalize(没终结器)、FrankClassWithDestructor(定义了析构函数)。
具体代码如下:
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Data;
5using System.Data.Odbc;
6using System.Drawing;
7//Coded By Frank Xu Lei 18/2/2009
8//Study the .NET Memory Management
9//Garbage Collector 垃圾收集器。可以根据策略在需要的时候回收托管资源,
10//但是GC不知道如何管理非托管资源。如网络连接、数据库连接、画笔、组件等
11//两个机制来解决非托管资源的释放问题。析构函数、IDispose接口
12//COM引用计数
13//C++手动管理,New Delete
14//VB自动管理
15namespace MemoryManagement
16{
17 //继承接口IDisposable,实现Dispose方法,可以释放FrankClassDispose的实例资源
18 public class FrankClassWithDispose : IDisposable
19 {
20 private OdbcConnection _odbcConnection = null;
21
22 //构造函数
23 public FrankClassWithDispose()
24 {
25 if (_odbcConnection == null)
26 _odbcConnection = new OdbcConnection();
27 Console.WriteLine("FrankClassWithDispose has been created ");
28 }
29 //测试方法
30 public void DoSomething()
31 {
32
33 ////code here to do something
34 return ;
35 }
36 //实现Dispose,释放本类使用的资源
37 public void Dispose()
38 {
39 if (_odbcConnection != null)
40 _odbcConnection.Dispose();
41 Console.WriteLine("FrankClassWithDispose has been disposed");
42 }
43 }
44 //没有实现Finalize,等着GC回收FrankClassFinalize的实例资源,GC运行时候直接回收45 public class FrankClassNoFinalize46 {47 private OdbcConnection _odbcConnection = null;48 //构造函数49 public FrankClassNoFinalize()50 {51 if (_odbcConnection == null)52 _odbcConnection = new OdbcConnection();53 Console.WriteLine("FrankClassNoFinalize has been created");54 }55 //测试方法56 public void DoSomething()57 {5859 //GC.Collect();60 ////code here to do something61 return ;62 }63 }64 //实现析构函数,编译为Finalize方法,调用对象的析构函数65 //GC运行时,两次调用,第一次没释放资源,第二次才释放66 //FrankClassDestructor的实例资源67 //CLR使用独立的线程来执行对象的Finalize方法,频繁调用会使性能下降68 public class FrankClassWithDestructor69 {70 private OdbcConnection _odbcConnection = null;71 //构造函数72 public FrankClassWithDestructor()73 {74 if (_odbcConnection == null)75 _odbcConnection = new OdbcConnection();76 Console.WriteLine("FrankClassWithDestructor has been created");77 }78 //测试方法79 public void DoSomething()80 {81 ////code here to do something8283 return ;84 }85 //析构函数,释放未托管资源86 ~FrankClassWithDestructor()87 {88 if (_odbcConnection != null)89 _odbcConnection.Dispose();90 Console.WriteLine("FrankClassWithDestructor has been disposed");91 }92 }93}94
其中使用了非托管的对象OdbcConnection的实例。建立的客户端进行了简单的测试。客户端代码如下: