您的位置:首页 > 其它

.net内存回收与Dispose﹐Close﹐Finalize方法

2010-06-05 07:44 288 查看
一. net的对象使用普通分为三种情况﹕

1.创立对象
2.使用对象
3.开释对象

二.创立对象
1.创立对象实际分为两个步骤﹕变量类型宣布和初始化对象

2.变量类型宣布(declare),如﹕

FileStream fs
这行代码会在当前的变量作用域空间(栈或堆)里建立一个叫做fs的变量﹐至多四个字节吧(由于要存一个对象的地址)

3.初始化对象
对象在使用(调用其办法或属性)前﹐必需进行初始化。
如﹕

fs = new FileStream(@"C:/test.txt",FileMode.OpenOrCreate);
这行代码会分红3个步骤﹕
a.在托管堆中分配一块内存﹐其大小等于FileStream中一切字段(当然不包括动态的)的内存总和加上MS以为需求的其它东东。
b.初始化对象的字段(值类型的把其位全部初始化成0,对象初始化为null﹐当然string是一个例外﹐它被初始化成空字符串)
c.调用FileStream相应的构造器﹐这里会初始化一个非托管资源(文件)的公有字段。

三.使用对象
使用对象就没什么讲的﹐就是调用对象的办法(或属性等)来完成某个功能当然为了开释对象而调用的办法其范畴应不属于此类中(如今提到的Finalize等)

四.开释对象
1.开释对象也就是说这个对象我已经不需求了﹐如今我要把其开释﹐以便把其在堆上所占用的内存空间给发出来(当然变量名的内存空间就不需求管了﹐由于它会随其作用域主动消失)

2. .net主动进行内存管理﹐也就是说当它判别一个对象没有用了(当然有本人的算法)﹐它就会将其内存给主动发出来﹐但是其发出的工夫普通不肯定(当.net以为内存紧张时﹐它就会开端)

BTW:其实我们就是想本人发出对象的内存也不能够﹐由于MS没有提供道路(GC.Collect也是发动.net的内存搜集功能)

五.第一个结论
在net中使用对象很简单﹐创立对象之后直接使用就能够了﹐不必了也不要去管它﹐渣滓搜集器会帮你把内存要回来的。

六.例外
当对象的成员引用了一个非托管资源时(不在托管堆上分配的内存或资源﹐像文件﹐数据库连接等等)﹐上面以一个例子来说明﹕
System.IO.FileStream类别﹐这是.net根本类库提供的一个非托管资源(文件)封装对象(用Reflector工具反编译mscorlib.dll可见其代码)

1.FileStream毫无疑问封装了一个非托管资源

观其源代码发觉有这样一个公有成员﹕

private SafeFileHandle _handle;
经过构造器调用的Init办法能够发觉这个成员的初始化代码﹕

this._handle = Win32Native.SafeCreateFile(text2, num1, share, secAttrs, mode, num2,
Win32Native.NULL);
然后者实际上就是kernel32.dll中的CreateFile办法﹐它前往一个HANDLE(即非托管资源引用)

2.我们先来使用这个类别﹕

1using System;
2using System.IO;
3
4public class TestFileStream
5{
6 public static void Main(string[] args)
7 {
8 //创立一个FileStream对象
9 FileStream fs = new FileStream(@"C:/test.txt",FileMode.OpenOrCreate);
10 Console.WriteLine("您能够尝试在零碎中删除c盘下的test.txt(回车键继续)");
11 //暂停程序施行﹐并尝试在零碎中删除那个文件
12 Console.ReadLine();
13
14 //删除文件测试
15 try
16 {
17 File.Delete(@"c:/test.txt");
18 }
19 catch (IOException ex)
20 {
21 Console.WriteLine("[Error]程序删除文件失败﹕{0}",ex.Message);
22 }
23 }
24}

3.在程序挂起时(Console.ReadLine等待输出)﹐删除文件会失败﹐很简单理解﹐由于文件打开后没有将其关闭﹐零碎不晓得这个文件能否还有用﹐所以帮我们保护这个文件(天经地义﹐那个非托管资源所使用的内存还被程序占用着)

4.但是在程序施行完后﹐我们再尝试删除文件﹐成功﹗为什么?(fs不是没相关闭那个SafeFileHandle吗?)
当然您能够说﹐windows操作零碎在一个进程结束后会主动回收其资源﹐没错(但是假如是com就惨了﹐由于com是具有于本人的独立进程内﹐而操作零碎不担任这个:( )﹐不过这里不是由于windows操作零碎的功能﹐而是.net渣滓搜集器帮的忙。

5.看上面这个例子

1using System;
2using System.IO;
3
4public class TestFileStream
5{
6 public static void Main(string[] args)
7 {
8 //创立一个FileStream对象
9 FileStream fs = new FileStream(@"C:/test.txt", FileMode.OpenOrCreate);
10 Console.WriteLine("您能够尝试在零碎中删除c盘下的test.txt(回车键继续)");
11 //暂停程序施行﹐并尝试在零碎中删除那个文件
12 Console.ReadLine();
13
14 /**//*进行渣滓搜集*/
15 GC.Collect();
16 Console.WriteLine("再删一下试试");
17 Console.ReadLine();
18 }
19}
6.留意两头那行代码:

GC.Collect();
这是强迫要.net渣滓搜集器进行渣滓搜集。
我们再去尝试删除test.txt﹐竟然能够被删除了﹐为什么呀?(fs不是没相关闭那个SafeFileHandle吗?)﹐让我细细道来﹕

7.我们首先理解一下.net渣滓搜集器进行渣滓搜集的四种机遇(参见﹕.net框架程序设想 李建忠译)
a.最罕见的﹕当.net觉得合适时﹐例如它感到内存紧张了(朮语称为﹕0代对象充溢)
b.微软激烈不建议使用的﹕GC的Collect办法调用(就是我们上面用的这种啦﹐由于会降低功能﹐会挂起进程, 等等﹐反正听微软的吧。当然某些时分能够用﹐就像我上面用来测试的代码﹐呵呵...)
c.使用程序域卸载时(AppDomain)
d.CLR被关闭时

8.如今我们能够明白第1个例子为什么在程序结束后文件能够被删除﹐由于CLR被关闭时﹐.net施行了渣滓搜集(也就是同等于第二个例子的GC.Collect()代码)

9.所以如今一切的成绩都集合到渣滓搜集上面﹐它做了什么?

a.渣滓搜集器在判别一个对象不会再被引用到后﹐就开端对它进行渣滓搜集(即回收内存)
b.清空内存(即把托管堆中的内存发出来)
c.但是对象的有些字段引用到了非托管资源怎样办?如FileStream的_handle
d.所以我们必需告诉渣滓搜集器﹐在你回收我的内存之前﹐先帮我施行一个办法来发出我的非托管资源﹐免得托管堆的内存被你回收了﹐而我引用的非托管资源的内存却被泄漏了。
e.这个办法就是Finalize()﹐也就是C#的 ~ClassName() 办法(同C++中的析构语法)
f.所以一个对象假如具有Finalize办法时﹐渣滓搜集器在发出它的内存之前就会主动调用这个办法
g.这样我们就能够把那些东东(非托管资源)给清算干净了

由此看来﹐渣滓搜集器提供的这种机制就是为了更好的完美.net的主动内存管理的功能﹐让我们也能够参与到渣滓搜集合去

10.我们再来看看GC.Collect()这行代码或CLR关闭时.Net做了什么﹕
a.渣滓搜集器发动﹐发觉fs引用的那个对象已经没用了(当然CLR关闭时才不管你有没有用﹐通通回收)﹐于是对它进行内存回收
b.发觉fs的类型﹕FileStream提供了Finalize办法﹐于是先调用这个办法
(以下经过Reflector继续)
c.Finalize办法中有 this._handle.Dispose()代码﹐于是调用SafeHandler.Dispose()
d.接着转到(当然好多个圈﹐您悠着点...)SafeFileHandle.ReleaseHandle办法﹐发觉代码﹕Win32Native.CloseHandle() (即关闭非托管资源--文件HANDLE)

水落石出﹕本来是渣滓搜集器帮我们关闭了那个非托管资源(当然还是经过我们本人写的Finalize办法)﹐因然后面就能够删除文件了。

11.有人会问﹕好像我们平常在使用FileStream对象时﹐没这么繁杂呀?
答﹕Very Good!

一部分人﹕是由于大家都和我的例1一样有好运气﹐那个C盘下的test.txt文件自从被创立后﹐我压根就不会再去用它﹐管它这部分资源有没有被泄漏﹐有没有被锁定﹐最初程序结束时﹐被渣滓搜集器帮了忙﹐把忘了关闭的文件HANDLE给发出来了。

剩下的一部分人﹕在程序里埋下了一颗"哑弹"﹐不知什么时分会爆炸﹐就像我例子中的File.Delete办法就出现了异常。

(不过我觉得)绝大多数人﹕是在看了很多诸如.net编程警告﹐Microsoft激烈建议﹐MSDN标准做法等等等等( 还有我这篇blog﹐呵呵)之后﹐晓得了在使用如FileStream,SqlConnection这些东东时﹐必需将其Close。

12.Close与Dispose
检查我们那两个例子的代码﹐都是不标准的﹐正确做法该当在使用完那个FileStream后﹐调用fs.Close()将其关闭﹐以保证资源的安全。

附﹕正确做法

1using System;
2using System.IO;
3
4public class TestFileStream
5{
6 public static void Main(string[] args)
7 {
8 //创立一个FileStream对象
9 FileStream fs = new FileStream(@"C:/test.txt", FileMode.OpenOrCreate);
10
11 /**//*在用完FileStream后关闭*/
12 fs.Close();
13
14 //删除文件测试
15 try
16 {
17 File.Delete(@"c:/test.txt");
18 }
19 catch (IOException ex)
20 {
21 Console.WriteLine("[Error]程序删除文件失败﹕{0}", ex.Message);
22 }
23 }
24}

13.有人举手﹐讲这么多﹐早告诉我调用fs.Close不就得了。
哥们﹐fs.Close()办法是由您写的﹐调不调用﹐手在您身上﹐您不调用的话﹐哪天程序出了成绩﹐您有会叫﹕微软真渣滓﹐.net真不稳定﹐还是Java好﹐安全﹐可靠... 为防您的国骂﹐MS只好在渣滓搜集合加这一款﹐以防不测...

14.Dispose模式
认真检查.net类库中的那些根本类别﹐但凡有Finalize办法的类别﹐根本上都提供了诸如Dispose,Close,Dispose(bool)等办法(FileStream也不例外)

15.其实不管是Dispose,Close,Finalize办法﹐最终该当都是施行相反的代码
区别﹕
Finalize办法﹕只能由微软调用
Dispose和Close办法﹕提供给您调用
因而在您使用完那些类别后﹐那就直接调用Close吧(没有Close﹐再调用Dispose办法)﹐当然万一您忘了﹐也别担心﹐还有渣滓搜集器帮您垫后。

七.第二个结论﹕
1.在您开发一个封装非托管资源(即类中的字段引用到了非托管资源)的类别时﹕
A:激烈建议您提供Finalize办法进行非托管资源的开释﹐.net渣滓搜集器不会帮您主动回收那部分资源﹐而是经过调用您的Finalize办法来帮您开释。(这样能够保证﹕在使用您类别的那位程序员忘了手动回收内存时﹐还可经过渣滓搜集器来弥补)

B.激烈建议您提供一个Close或Dispose办法﹐以便使用您类别的程序员能够手动开释您的类别中的非托管资源。(参见.net框架程序设想 主动内存管理一章完成Dispose模式)

C.假如类别封装了像FileStream这样的对象(即对非托管资源的再次封装)时﹐普通也该当提供一 个Close或Dispose办法﹐除非您的这个成员保证在每次使用后﹐都被正常的关闭﹐即对调用者通明。

2.在您使用一个封装非托管资源的类别时﹕
A:激烈建议您在明白晓得这个类别没有用之后﹐调用其提供的Close或Dispose办法手动开释其非托管资源的 内存。有道是﹕有借有还﹐再借不难;借了不还﹐再借休想~~

B:留意在手动开释后﹐不要再调用该对象的相关办法了﹐由于对象已经损毁了

再次BTW:不管是Finalize﹐Close还是Dispose﹐您都无法显式开释托管堆内存﹐它们永久是微软的"公家财富 "﹕)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: