net内存回收與Dispose﹐Close﹐Finalize方法
發表時(shí)間:2020-10-18
發布人(rén):融晨科技
浏覽次數:83
一. net的(de)對象使用一般分爲(wéi / wèi)三種情況﹕
1.創建對象
2.使用對象
3.釋放對象
二.創建對象
1.創建對象實際分爲(wéi / wèi)兩個(gè)步驟﹕變量類型宣告和(hé / huò)初始化對象
2.變量類型宣告(declare),如﹕
複制 保存
FileStream fs
這(zhè)行代碼會在(zài)當前的(de)變量作用域空間(棧或堆)裏建立一個(gè)叫做fs的(de)變量﹐至少四個(gè)字節吧(因爲(wéi / wèi)要(yào / yāo)存一個(gè)對象的(de)地(dì / de)址)
3.初始化對象
對象在(zài)使用(調用其方法或屬性)前﹐必須進行初始化。
如﹕
複制 保存
fs = new FileStream(@"C: est.txt",FileMode.OpenOrCreate);
這(zhè)行代碼會分成3個(gè)步驟﹕
a.在(zài)托管堆中分配一塊内存﹐其大(dà)小等于(yú)FileStream中所有字段(當然不(bù)包括靜态的(de))的(de)内存總和(hé / huò)加上(shàng)MS認爲(wéi / wèi)需要(yào / yāo)的(de)其它東東。
b.初始化對象的(de)字段(值類型的(de)把其位全部初始化成0,對象初始化爲(wéi / wèi)null﹐當然string是(shì)一個(gè)例外﹐它被初始化成空字符串)
c.調用FileStream相應的(de)構造器﹐這(zhè)裏會初始化一個(gè)非托管資源(文件)的(de)私有字段。
三.使用對象
使用對象就(jiù)沒什麽講的(de)﹐就(jiù)是(shì)調用對象的(de)方法(或屬性等)來(lái)完成某個(gè)功能當然爲(wéi / wèi)了(le/liǎo)釋放對象而(ér)調用的(de)方法其範疇應不(bù)屬于(yú)此類中(現在(zài)提到(dào)的(de)Finalize等)
四.釋放對象
1.釋放對象也(yě)就(jiù)是(shì)說(shuō)這(zhè)個(gè)對象我已經不(bù)需要(yào / yāo)了(le/liǎo)﹐現在(zài)我要(yào / yāo)把其釋放﹐以(yǐ)便把其在(zài)堆上(shàng)所占用的(de)内存空間給收回來(lái)(當然變量名的(de)内存空間就(jiù)不(bù)需要(yào / yāo)管了(le/liǎo)﹐因爲(wéi / wèi)它會随其作用域自動消失)
2. .net自動進行内存管理﹐也(yě)就(jiù)是(shì)說(shuō)當它判斷一個(gè)對象沒有用了(le/liǎo)(當然有自己的(de)算法)﹐它就(jiù)會将其内存給自動收回來(lái)﹐但是(shì)其收回的(de)時(shí)間一般不(bù)确定(當.net認爲(wéi / wèi)内存緊張時(shí)﹐它就(jiù)會開始)
BTW:其實我們就(jiù)是(shì)想自己收回對象的(de)内存也(yě)不(bù)可能﹐因爲(wéi / wèi)MS沒有提供途徑(GC.Collect也(yě)是(shì)啓動.net的(de)内存收集功能)
五.第一個(gè)結論
在(zài)net中使用對象很簡單﹐創建對象之(zhī)後直接使用就(jiù)可以(yǐ)了(le/liǎo)﹐不(bù)用了(le/liǎo)也(yě)不(bù)要(yào / yāo)去管它﹐垃圾收集器會幫你把内存要(yào / yāo)回來(lái)的(de)。
六.例外
當對象的(de)成員引用了(le/liǎo)一個(gè)非托管資源時(shí)(不(bù)在(zài)托管堆上(shàng)分配的(de)内存或資源﹐像文件﹐數據庫連接等等)﹐下面以(yǐ)一個(gè)例子(zǐ)來(lái)說(shuō)明﹕
System.IO.FileStream類别﹐這(zhè)是(shì).net基本類庫提供的(de)一個(gè)非托管資源(文件)封裝對象(用Reflector工具反編譯mscorlib.dll可見其代碼)
1.FileStream毫無疑問封裝了(le/liǎo)一個(gè)非托管資源
觀其源代碼發現有這(zhè)樣一個(gè)私有成員﹕
複制 保存
private SafeFileHandle _handle;
通過構造器調用的(de)Init方法可以(yǐ)發現這(zhè)個(gè)成員的(de)初始化代碼﹕
複制 保存
this._handle = Win32Native.SafeCreateFile(text2, num1, share, secAttrs, mode, num2,
Win32Native.NULL);
而(ér)後者實際上(shàng)就(jiù)是(shì)kernel32.dll中的(de)CreateFile方法﹐它返回一個(gè)HANDLE(即非托管資源引用)
2.我們先來(lái)使用這(zhè)個(gè)類别﹕
複制 保存
using System;
using System.IO;
public class TestFileStream
{
public static void Main(string[] args)
{
//創建一個(gè)FileStream對象
FileStream fs = new FileStream(@"C: est.txt", FileMode.OpenOrCreate);
Console.WriteLine("您可以(yǐ)嘗試在(zài)系統中删除c盤下的(de)test.txt(回車鍵繼續)");
//暫停程序執行﹐并嘗試在(zài)系統中删除那個(gè)文件
Console.ReadLine();
//删除文件測試
try
{
File.Delete(@"c: est.txt");
}
catch (IOException ex)
{
Console.WriteLine("[Error]程序删除文件失敗﹕{0}", ex.Message);
}
}
}
3.在(zài)程序挂起時(shí)(Console.ReadLine等待輸入)﹐删除文件會失敗﹐很容易理解﹐因爲(wéi / wèi)文件打開後沒有将其關閉﹐系統不(bù)知道(dào)這(zhè)個(gè)文件是(shì)否還有用﹐所以(yǐ)幫我們保護這(zhè)個(gè)文件(理所當然﹐那個(gè)非托管資源所使用的(de)内存還被程序占用着)
4.但是(shì)在(zài)程序執行完後﹐我們再嘗試删除文件﹐成功﹗爲(wéi / wèi)什麽?(fs不(bù)是(shì)沒有關閉那個(gè)SafeFileHandle嗎?)
當然您可以(yǐ)說(shuō)﹐windows操作系統在(zài)一個(gè)進程結束後會自動回收其資源﹐沒錯(但是(shì)如果是(shì)com就(jiù)慘了(le/liǎo)﹐因爲(wéi / wèi)com是(shì)存在(zài)于(yú)自己的(de)獨立進程内﹐而(ér)操作系統不(bù)負責這(zhè)個(gè) )﹐不(bù)過這(zhè)裏不(bù)是(shì)因爲(wéi / wèi)windows操作系統的(de)功能﹐而(ér)是(shì).net垃圾收集器幫的(de)忙。
5.看下面這(zhè)個(gè)例子(zǐ)
複制 保存
using System;
using System.IO;
public class TestFileStream
{
public static void Main(string[] args)
{
//創建一個(gè)FileStream對象
FileStream fs = new FileStream(@"C: est.txt", FileMode.OpenOrCreate);
Console.WriteLine("您可以(yǐ)嘗試在(zài)系統中删除c盤下的(de)test.txt(回車鍵繼續)");
//暫停程序執行﹐并嘗試在(zài)系統中删除那個(gè)文件
Console.ReadLine();
/*進行垃圾收集*/
GC.Collect();
Console.WriteLine("再删一下試試");
Console.ReadLine();
}
}
6.注意中間那行代碼:
複制 保存
GC.Collect();
這(zhè)是(shì)強制要(yào / yāo).net垃圾收集器進行垃圾收集。
我們再去嘗試删除test.txt﹐居然可以(yǐ)被删除了(le/liǎo)﹐爲(wéi / wèi)什麽呀?(fs不(bù)是(shì)沒有關閉那個(gè)SafeFileHandle嗎?)﹐讓我細細道(dào)來(lái)﹕
7.我們首先了(le/liǎo)解一下.net垃圾收集器進行垃圾收集的(de)四種時(shí)機(參見﹕.net框架程序設計 李建忠譯)
a.最常見的(de)﹕當.net覺得合适時(shí)﹐例如它感到(dào)内存緊張了(le/liǎo)(朮語稱爲(wéi / wèi)﹕0代對象充滿)
b.微軟強烈不(bù)建議使用的(de)﹕GC的(de)Collect方法調用(就(jiù)是(shì)我們上(shàng)面用的(de)這(zhè)種啦﹐因爲(wéi / wèi)會降低性能﹐會挂起進程, 等等﹐反正聽微軟的(de)吧。當然某些時(shí)候可以(yǐ)用﹐就(jiù)像我上(shàng)面用來(lái)測試的(de)代碼﹐呵呵...)
c.應用程序域卸載時(shí)(AppDomain)
d.CLR被關閉時(shí)
8.現在(zài)我們可以(yǐ)明白第1個(gè)例子(zǐ)爲(wéi / wèi)什麽在(zài)程序結束後文件可以(yǐ)被删除﹐因爲(wéi / wèi)CLR被關閉時(shí)﹐.net執行了(le/liǎo)垃圾收集(也(yě)就(jiù)是(shì)等同于(yú)第二個(gè)例子(zǐ)的(de)GC.Collect()代碼)
9.所以(yǐ)現在(zài)所有的(de)問題都集中到(dào)垃圾收集上(shàng)面﹐它做了(le/liǎo)什麽?
a.垃圾收集器在(zài)判斷一個(gè)對象不(bù)會再被引用到(dào)後﹐就(jiù)開始對它進行垃圾收集(即回收内存)
b.清空内存(即把托管堆中的(de)内存收回來(lái))
c.但是(shì)對象的(de)有些字段引用到(dào)了(le/liǎo)非托管資源怎麽辦?如FileStream的(de)_handle
d.所以(yǐ)我們必須告訴垃圾收集器﹐在(zài)你回收我的(de)内存之(zhī)前﹐先幫我執行一個(gè)方法來(lái)收回我的(de)非托管資源﹐以(yǐ)免托管堆的(de)内存被你回收了(le/liǎo)﹐而(ér)我引用的(de)非托管資源的(de)内存卻被洩漏了(le/liǎo)。
e.這(zhè)個(gè)方法就(jiù)是(shì)Finalize()﹐也(yě)就(jiù)是(shì)C#的(de) ~ClassName() 方法(同C++中的(de)析構語法)
f.所以(yǐ)一個(gè)對象如果存在(zài)Finalize方法時(shí)﹐垃圾收集器在(zài)收回它的(de)内存之(zhī)前就(jiù)會自動調用這(zhè)個(gè)方法
g.這(zhè)樣我們就(jiù)可以(yǐ)把那些東東(非托管資源)給清理幹淨了(le/liǎo)
由此看來(lái)﹐垃圾收集器提供的(de)這(zhè)種機制就(jiù)是(shì)爲(wéi / wèi)了(le/liǎo)更好的(de)完善.net的(de)自動内存管理的(de)功能﹐讓我們也(yě)可以(yǐ)參與到(dào)垃圾收集中去
10.我們再來(lái)看看GC.Collect()這(zhè)行代碼或CLR關閉時(shí).Net做了(le/liǎo)什麽﹕
a.垃圾收集器啓動﹐發現fs引用的(de)那個(gè)對象已經沒用了(le/liǎo)(當然CLR關閉時(shí)才不(bù)管你有沒有用﹐通通回收)﹐于(yú)是(shì)對它進行内存回收
b.發現fs的(de)類型﹕FileStream提供了(le/liǎo)Finalize方法﹐于(yú)是(shì)先調用這(zhè)個(gè)方法
(以(yǐ)下通過Reflector繼續)
c.Finalize方法中有 this._handle.Dispose()代碼﹐于(yú)是(shì)調用SafeHandler.Dispose()
d.接着轉到(dào)(當然好多個(gè)圈﹐您悠着點...)SafeFileHandle.ReleaseHandle方法﹐發現代碼﹕Win32Native.CloseHandle() (即關閉非托管資源--文件HANDLE)
真相大(dà)白﹕原來(lái)是(shì)垃圾收集器幫我們關閉了(le/liǎo)那個(gè)非托管資源(當然還是(shì)通過我們自己寫的(de)Finalize方法)﹐因此後面就(jiù)可以(yǐ)删除文件了(le/liǎo)。
11.有人(rén)會問﹕好像我們平時(shí)在(zài)使用FileStream對象時(shí)﹐沒這(zhè)麽複雜呀?
答﹕Very Good!
一部分人(rén)﹕是(shì)因爲(wéi / wèi)大(dà)家都和(hé / huò)我的(de)例1一樣有好運氣﹐那個(gè)C盤下的(de)test.txt文件自從被創建後﹐我壓根就(jiù)不(bù)會再去用它﹐管它這(zhè)部分資源有沒有被洩漏﹐有沒有被鎖定﹐最後程序結束時(shí)﹐被垃圾收集器幫了(le/liǎo)忙﹐把忘了(le/liǎo)關閉的(de)文件HANDLE給收回來(lái)了(le/liǎo)。
剩下的(de)一部分人(rén)﹕在(zài)程序裏埋下了(le/liǎo)一顆"啞彈"﹐不(bù)知什麽時(shí)候會爆炸﹐就(jiù)像我例子(zǐ)中的(de)File.Delete方法就(jiù)出(chū)現了(le/liǎo)異常。
(不(bù)過我覺得)絕大(dà)多數人(rén)﹕是(shì)在(zài)看了(le/liǎo)很多諸如.net編程忠告﹐Microsoft強烈建議﹐MSDN标準做法等等等等( 還有我這(zhè)篇blog﹐呵呵)之(zhī)後﹐知道(dào)了(le/liǎo)在(zài)使用如FileStream,SqlConnection這(zhè)些東東時(shí)﹐必須将其Close。
12.Close與Dispose
查看我們那兩個(gè)例子(zǐ)的(de)代碼﹐都是(shì)不(bù)标準的(de)﹐正确做法應該在(zài)使用完那個(gè)FileStream後﹐調用fs.Close()将其關閉﹐以(yǐ)保證資源的(de)安全。
附﹕正确做法
複制 保存
using System;
using System.IO;
public class TestFileStream
{
public static void Main(string[] args)
{
//創建一個(gè)FileStream對象
FileStream fs = new FileStream(@"C: est.txt", FileMode.OpenOrCreate);
/*在(zài)用完FileStream後關閉*/
fs.Close();
//删除文件測試
try
{
File.Delete(@"c: est.txt");
}
catch (IOException ex)
{
Console.WriteLine("[Error]程序删除文件失敗﹕{0}", ex.Message);
}
}
}
13.有人(rén)舉手﹐講這(zhè)麽多﹐早告訴我調用fs.Close不(bù)就(jiù)得了(le/liǎo)。
哥們﹐fs.Close()方法是(shì)由您寫的(de)﹐調不(bù)調用﹐手在(zài)您身上(shàng)﹐您不(bù)調用的(de)話﹐哪天程序出(chū)了(le/liǎo)問題﹐您有會叫﹕微軟真垃圾﹐.net真不(bù)穩定﹐還是(shì)java好﹐安全﹐可靠... 爲(wéi / wèi)防您的(de)國(guó)罵﹐MS隻好在(zài)垃圾收集中加這(zhè)一款﹐以(yǐ)防不(bù)測...
14.Dispose模式
認真查看.net類庫中的(de)那些基本類别﹐凡是(shì)有Finalize方法的(de)類别﹐基本上(shàng)都提供了(le/liǎo)諸如Dispose,Close,Dispose(bool)等方法(FileStream也(yě)不(bù)例外)
15.其實不(bù)管是(shì)Dispose,Close,Finalize方法﹐最終應該都是(shì)執行相同的(de)代碼
區别﹕
Finalize方法﹕隻能由微軟調用
Dispose和(hé / huò)Close方法﹕提供給您調用
因此在(zài)您使用完那些類别後﹐那就(jiù)直接調用Close吧(沒有Close﹐再調用Dispose方法)﹐當然萬一您忘了(le/liǎo)﹐也(yě)别擔心﹐還有垃圾收集器幫您墊後。
七.第二個(gè)結論﹕
1.在(zài)您開發一個(gè)封裝非托管資源(即類中的(de)字段引用到(dào)了(le/liǎo)非托管資源)的(de)類别時(shí)﹕
A:強烈建議您提供Finalize方法進行非托管資源的(de)釋放﹐.net垃圾收集器不(bù)會幫您自動回收那部分資源﹐而(ér)是(shì)通過調用您的(de)Finalize方法來(lái)幫您釋放。(這(zhè)樣可以(yǐ)保證﹕在(zài)使用您類别的(de)那位程序員忘了(le/liǎo)手動回收内存時(shí)﹐還可通過垃圾收集器來(lái)補救)
B.強烈建議您提供一個(gè)Close或Dispose方法﹐以(yǐ)便使用您類别的(de)程序員可以(yǐ)手動釋放您的(de)類别中的(de)非托管資源。(參見.net框架程序設計 自動内存管理一章實現Dispose模式)
C.如果類别封裝了(le/liǎo)像FileStream這(zhè)樣的(de)對象(即對非托管資源的(de)再次封裝)時(shí)﹐一般也(yě)應該提供一 個(gè)Close或Dispose方法﹐除非您的(de)這(zhè)個(gè)成員保證在(zài)每次使用後﹐都被正常的(de)關閉﹐即對調用者透明。
2.在(zài)您使用一個(gè)封裝非托管資源的(de)類别時(shí)﹕
A:強烈建議您在(zài)明确知道(dào)這(zhè)個(gè)類别沒有用之(zhī)後﹐調用其提供的(de)Close或Dispose方法手動釋放其非托管資源的(de) 内存。有道(dào)是(shì)﹕有借有還﹐再借不(bù)難;借了(le/liǎo)不(bù)還﹐再借休想~~
B:注意在(zài)手動釋放後﹐不(bù)要(yào / yāo)再調用該對象的(de)相關方法了(le/liǎo)﹐因爲(wéi / wèi)對象已經損毀了(le/liǎo)
再次BTW:不(bù)管是(shì)Finalize﹐Close還是(shì)Dispose﹐您都無法顯式釋放托管堆内存﹐它們永遠是(shì)微軟的(de)"私人(rén)财産 "﹕)
有人(rén)議論:
.net 不(bù)要(yào / yāo)把對象 = null 的(de);
一位在(zài)一般情況下.net的(de)的(de)一個(gè)變量如
FileStream fs = new FileStream(@"C:\test.txt", FileMode.OpenOrCreate);
這(zhè)個(gè) fs 類似c 語言裏的(de)指針,隻是(shì)一個(gè)地(dì / de)址而(ér)已
= null 是(shì)沒啥用的(de)
如果等于(yú)null 反倒影響gc 回收了(le/liǎo)
還有.net Windows 程序
和(hé / huò) ASP.NET 下 GC 的(de)回收也(yě)許會有些不(bù)同
所以(yǐ)一個(gè)這(zhè)樣的(de)列子(zǐ)不(bù)會完全說(shuō)明問題的(de)。
還有如個(gè)cpu占有率比較高的(de)情況下 GC 也(yě)許回收對象很慢
要(yào / yāo)比正常情況下慢很多。
原則上(shàng)還是(shì)、能 Dispose 的(de)類就(jiù)要(yào / yāo) Dispose
類似FileStream 的(de)對象如果不(bù)在(zài)後面的(de)代碼中使用,不(bù)用 close 直接
Dispose 即可,Dispose 隐含 close 的(de)其實
數據連接對象也(yě)是(shì)推薦使用 using 代碼塊自動釋放以(yǐ)防止中途出(chū)現異常
雲南軟件開發公司中的(de)佼佼者,緻力定制軟件開發,雲南軟件開發請聯系,電話:15987118523。
昆明軟件開發公司中的(de)佼佼者,緻力定制軟件開發,昆明軟件開發請聯系,電話:15987118523。