1..Net的类型和内存分配
Net中的所有类型都是(直接或间接)从System.Object类型派生的。
CTS中的类型被分成两大类——引用类型(reference type,又叫托管类型[managed type]),分配在内存堆上,值类型(value type)。值类型分配在堆栈上。如图
值类型在栈里,先进后出,值类型变量的生命有先后顺序,这个确保了值类型变量在推出作用域以前会释放资源。比引用类型更简单和高效。堆栈是从高地址往低地址分配内存。
引用类型分配在托管堆(Managed Heap)上,声明一个变量在栈上保存,当使用new创建对象时,会把对象的地址存储在这个变量里。托管堆相反,从低地址往高地址分配内存,如图
2.GC垃圾收集器的工作原理
上图中,当dataSet使用过期以后,我们不显示销毁对象,堆上的对象还继续存在,等待GC的 回收。
在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 FrankClassNoFinalize
46 {
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 {
58
59 //GC.Collect();
60 ////code here to do something
61 return ;
62 }
63 }
64 //实现析构函数,编译为Finalize方法,调用对象的析构函数
65 //GC运行时,两次调用,第一次没释放资源,第二次才释放
66 //FrankClassDestructor的实例资源
67 //CLR使用独立的线程来执行对象的Finalize方法,频繁调用会使性能下降
68 public class FrankClassWithDestructor
69 {
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 something
82
83 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的实例。建立的客户端进行了简单的测试。客户端代码如下:
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Data;
5using MemoryManagement;
6//Coded By Frank Xu Lei 18/2/2009
7//Study the .NET Memory Management
8//Test The Unmanaged Objects Reclaimed.
9//针对非托管代码的测试,比较
10//托管代码,GC可以更具策略自己回收,也可以实现IDisposable,调用Dispose()方法,主动释放。
11namespace MemoryManagementClient
12{
13 class Program
14 {
15 static void Main(string[] args)
16 {
17
18 /////////////////////////////////////////(1)////////////////////////////////////////////
19 //调用Dispose()方法,主动释放。资源,灵活
20 FrankClassWithDispose _frankClassWithDispose = null;
21 try
22 {
23 _frankClassWithDispose = new FrankClassWithDispose();
24 _frankClassWithDispose.DoSomething();
25
26 }
27 finally
28 {
29 if (_frankClassWithDispose!=null)
30 _frankClassWithDispose.Dispose();
31 //Console.WriteLine("FrankClassWithDispose实例已经被释放");
32 }
33
34 /////////////////////////////////////////(2)//////////////////////////////////////////////
35 //可以使用Using语句创建非托管对象,方法执行结束前,会调用
36 using (FrankClassWithDispose _frankClassWithDispose2 = new FrankClassWithDispose())
37 {
38 //_frankClassWithDispose2.DoSomething();
39 }
40
41 /////////////////////////////////////////(3)////////////////////////////////////////////
42 //垃圾收集器运行的时候,一次就释放资源
43 FrankClassNoFinalize _frankClassNoFinalize = new FrankClassNoFinalize();
44 _frankClassNoFinalize.DoSomething();
45
46 //////////////////////////////////////////(4)//////////////////////////////////////////////
47 //垃圾收集器运行的时候,两次才能够释放资源
48 FrankClassWithDestructor _frankClassWithDestructor = new FrankClassWithDestructor();
49 _frankClassWithDestructor.DoSomething();
50 ///////////////////////////////////////////(5)/////////////////////////////////////////////
51 //不能使用Using语句来创建对象,因为其没实现IDispose接口
52 //using (FrankClassWithDestructor _frankClassWithDestructor2 = new FrankClassWithDestructor())
53 //{
54 // _frankClassWithDestructor2.DoSomething();
55 //}
56
57 //////////////////////////////////////////////////////////////////////////////////////
58 //For Debug
59 Console.WriteLine("Press any key to continue");
60 Console.ReadLine();
61
62
63 }
64 }
65}
66
值得一提的是:调用Dispose()方法,主动释放资源,灵活,可以使用Using语句创建非托管对象,方法执行结束前,会调用
Dispose()方法释放资源,这两端代码的效果是一样的,可以查看编译后IL。
Using 语句有同样的效果,来实现非托管对象资源的释放。这点在面试中也会经常遇到,Using关键字的用法有哪几种等等类似的问题。基本理想的答案都是除了引用 命名空间,和命名空间设置别名外,就是这个用法实现如try finally块一样作用的对非托管对象资源的回收。只是一种简便的写法。
总结:看了本文以后,不知对你是否有所帮助,如果你理解了.net垃圾回收的机制和GC的工作原理,以及包含如何管理非托管资源,你就会成为一个内存管理 的高手。如果面试官问道这个问题,你就可以详细阐述你对这类问题的理解和看法。希望这篇文章能对你的工作和学习带来帮助~
DemoCodes/Files/frank_xl/MemoryManagement2008.zip
【作者】Frank Xu Lei
【地址】 http://www.cnblogs.com/frank_xl/archive/2009/02/19/1393566.html