引言:使用c++進行編程,內存的處理絕對是讓每個程序設計者最頭疼的一塊了。但是對於.net平台下使用c#語言開發系統,內存管理可以說已經不算是問題了。在.net平台下CLR負責管理內存,CLR中的垃圾收集器GC:Garbage Collection,負責執行內存的清理工作,但是GC也只是負責清理托管堆上的垃圾對象,而對於非托管的資源對象,GC則不起作用,必須要程序開發者手動清理。此處需要稍微說明:一般而言,非托管資源主要包括數據庫鏈接、文件句柄、COM對象、套接字、GDI+對象、互斥體等等。
在介紹GC前,有必要對.net中CLR管理內存區域做簡要介紹:
1、 堆棧:用於分配值類型實例。堆棧主要操作系統管理,而不受垃圾收集器的控制,當值類型實例所在方法結束時,其存儲單位自動釋放。棧的執行效率高,但存儲容量有限。
2 、GC堆:用於分配小對象實例。如果引用類型對象實例的大小小於85000字節,實例將被配置在GC堆上,當有內存分配或者回收時,垃圾收集器可能會對GC堆進行壓縮。
3、 LOH:large object heap,用於分配大對象實例。如果引用類型對象的實例的大小不小於85000字節時,該實例將被分配到LOH堆上,而LOH堆不會被壓縮,而且只在完全GC回收時被回收。
既然要清理垃圾,那么必然要明白什么是垃圾吧,垃圾的理解:一個對象成為“垃圾”表示該對象不被任何其他對象所引用。因此GC必須采用一定的算法在托管堆中遍歷所有對象,最終形成一個可達對象圖,而不可達的對象將成為被釋放的垃圾對象等待收集。
在明白了什么是垃圾后,肯定會對GC如何回收垃圾提出疑問。.net平台下,每個應用程序都有一組根(指針),它指向托管堆中的存儲位置,由JIT編譯器和CLR運行時維護根指針列表,主要包括全局變量、靜態變量、局部變量和寄存器指針等。GC正是通過根指針列表來獲得托管堆中的對象圖,其中定義了應用程序根引用的托管堆中的對象,當GC啟動時,它假設所有對象都是可回收的垃圾,開始遍歷所有的根,將根引用的對象標記為可達對象添加到可達對象圖中,在遍歷過程中,如果根引用的對象還引用着其他對象,則該對象也被添加到可達對象圖中,依次類推,GC通過根列表的遞歸遍歷,將能找到所有可達對象,並形成一個可達對象圖。同時那些不可達對象則被認為是可回收對象,GC接着運行垃圾收集進程來釋放垃圾對象的內存空間。這種收集算法稱為:標記和清除收集算法。
垃圾回收一般在下列情況下進行:
1 內存不足溢出時,更確切的應該說是第0代對象充滿時。
2 調用GC.Collect方法強制執行垃圾回收。(一般不要執行此方法)
3 Windows報告內存不足時,CLR將強制執行垃圾回收。
4 CLR卸載AppDomain時,GC將對所有代齡的對象執行垃圾回收。
5 其他情況,如物理內存不足,超出短期存活代的內存段門限,運行主機拒絕分配內存等。
垃圾回收運行機制:
垃圾收集器將托管堆中對象分為三代:0、1和2,在CLR初始化時,會選擇為三代設置不同的闕值容量,一般為:第0代大約為256KB,第1代2MB,第2代10MB。容量越大效率越低,而GC收集器會自動調節其闕值容量來提升執行效率。在CLR初始化后,首先添加到托管堆中的對象都被定位第0代對象,當有垃圾回收執行時,未被回收的對象代齡將提升一級,變成第1代對象,而后新建對象仍未第0代對象。代齡越小表示對象越新,通常情況下其生命周期也最短,因此GC總是先收集第0代的不可達對象內存。
隨着對象的不斷創建,垃圾收集再次啟動時則只會檢查0代對象並回收0代垃圾對象。而1代對象由於未達到1代容量闕值,則不會進行垃圾回收操作,從而有效地提高了垃圾收集的效率,而這也是代齡機制在垃圾回收中的性能優化作用。當第0代對象釋放的內存不足以創建新的對象,同時1代對象的體積也超出了容量闕值是,垃圾收集器將同時對0代和1代對象進行垃圾回收。回收之后,未被回收的1代對象變化2級對象,未被回收的0代對象升級為1代對象,而后新建的對象仍為第0代對象。
注:微軟強烈建議不要通過GC.Collect方法來強制執行垃圾收集,這樣會妨礙GC本身的工作方式,通過Collect會使對象代齡不斷提升,擾亂應用程序的內存使用。只有在明確知道有大量對象停止引用時,才考慮使用GC.Collect方法來調用收集器。
上面介紹了垃圾管理器GC清理托管資源所涉及的一些機理,然而對於非托管資源,需要開發者手動清理,方法主要有:Finalize方法和Dispose方法。
Finalize:
Finalize方法又稱為終止化操作:通過對自定義類型實現一個Finalize方法來釋放非托管資源,而終止化操作在對象的內存回收之前通過調用Finalize方法來釋放資源。在析構函數中重寫Finalize方法,當垃圾管理器啟動時,對於判定為可回收的垃圾對象,GC會自動執行其Finalize方法清理非托管資源。
protected override void Finalize()
{
try
{
//執行自定義資源清理操作
}
finally
{
base.Finalize();
}
}
Finalize的缺點是:
終止化操作的時間無法控制,執行順序也不能保證。
Finalize方法會極大的損失性能,GC使用一個終止話隊列的內部結構來跟蹤具有Finalize方法的對象。
重寫finalize方法的類型對象,其引用類型對象的代齡將被提升,帶來內存壓力。
Dispose:
Dispose模式的實現是:定義的類型必須實現System.IDisposable接口,該接口中定義了一個公有無參數的Dispose方法,程序設計者可以在Dispose方法中實現對非托管資源的清理工作。
下面編寫一個項目中遇到使用Dispose方法的例子,功能是在套接字使用完畢后釋放資源
public class SocketConnection : IDisposable
{
//邏輯操作
//.....................
//實現Dispose
public void Dispose()
{
try
{
this.ClientSock.Shutdown(SocketShutdown.Both);
this.ClientSock.Close();
this.Server = null;
}
catch (Exception ex)
{ }
}
}
總結:
在.net中,在堆棧上分配的資源在調用結束后,其內存自動會釋放。
托管堆中的資源,由CLR的垃圾管理器進行清理操作。
對於非托管資源,必須由程序設計者進行操作,而對於Finalize和Dispose,最好采用Dispose方法。

