【轉】C#的內存管理

學習C#的朋友應該知道C#編程的一個優點是程序員不需要像使用C/C++時那樣的關心具體的內存管理,因爲垃圾收集器會處理所有的內存清理工作。雖然不必手工管理內存,但如果要編寫高質量的代碼,還是要理解後臺發生的事情,理解C#的內存管理。本文主要介紹給變量分配內存時計算機內存中發生的情況。
C#將數據分爲兩種:值數據類型和引用數據類型,這兩種數據類型存儲在內存中的不同的地方:值數據類型存儲在堆棧中,而引用類型存儲在內存的託管堆中。
1、內存簡介
Windows使用一個系統:虛擬尋址系統。這個系統的作用是將程序可用的內存地址映射到硬件內存中的實際地址上。其實際結果就是32位的機子上每個進程都可以使用4GB的內存,當然,64位機這個數字就大了去了。這4GB的內存實際上包含了程序的所有的部分:可執行代碼,DLL以及程序運行時使用的所有變量的內容。這個4GB的內存成爲虛擬地址空間或虛擬內存。爲方便,這裏成爲內存。
 4GB中的每個存儲單元都是從零開始向上存儲的。要訪問存儲在內存中的某個空間中的值,就必須提供表示該存儲單元的一個數字。在高級編程語言中,編譯器的一個重要作用就是負責將人們可以理解的變量名稱變爲處理器可以理解的內存地址。
2、堆棧
在內存中,有一個區域成爲堆棧,存儲對象
對象成員的值數據類型調用方法時,傳遞給所有方法的參數的副本注意:調用方法時,堆棧存儲的是所有參數的副本,因此,經值類型A傳遞給函數,A的值是不會變化的。當然,引用類型是會變化的,因爲在堆棧中存儲的是引用類型的地址,這在後面會有詳細的介紹。
下面以一個例子來說明堆棧的工作方式,如下面的代碼:
{
   int a;
//dosomething;
{
int b;
//dosomething
}
}
首先聲明a,在內部的代碼塊中聲明b,然後內部的代碼塊終止,b就出了作用域,最後a出作用域。所以b的生命週期總是包含在a的生命週期內,在釋放變量的時候,其順序總是和分配內存的順序是相反的。即:變量的生存週期都是嵌套的。這就是堆棧的工作方式。
3、託管堆
堆棧具有相當高的性能,但是變量的生命週期必須是嵌套的,這個要求在有的時候過於苛刻。我們希望有一種別的方法來分配內存,存儲一些數據,並在方法退出的很長一段時間內,這些數據仍然是可用的,這時,就使用託管堆。
託管堆(簡稱堆)是內存中的另外一個區域,我們仍然用一個例子來說明堆的工作方式,如下面代碼:
{
Customercustomer1;
customer1=newCustomer();
Customercustomer2=new Customer();
//dosomething
}
首先,聲明一個Customer:customer1,在堆棧上給這個引用分配存儲控件。請注意:僅僅是給這個引用分配存儲空間,並不是實際的Customer對象。customer1佔用4個字節的空間(32位機),來表示Customer對象在內存中的地址。
然後,執行第二行代碼,完成以下操作:
在堆上分配存儲空間,用來存儲Customer對象,注意:這裏是Customer對像。
將變量customer1的值設爲分配給Customer對象的內存地址從這個例子中可以看出,建立引用類型的變量的過程要比獎勵值類型變量的過程複雜,且不避免的有性能的降低。但是,我們可以將一個引用變量的值賦給另一個引用變量,當一個變量出作用域時,它會從堆棧中刪除,但是對象的數據仍然保留在內存中,直到程序停止。
這樣,我們在將一個引用變量A傳遞給函數時,僅僅是將變量A的引用傳遞給了函數,即:僅僅是在堆棧上分配內存,即變量B兩者指向同一個內存地址。因此,當變量B發生變化時,變量A也會發生變化。
4、裝箱和拆箱
裝箱和拆箱就是值類型和引用類型的項目轉化,裝箱可以將值類型轉化爲引用類型,拆箱的作用正好相反,經引用類型轉化爲值類型。
5、垃圾收集
一般情況下。NET運行庫會在認爲需要的時候運行垃圾收集器來釋放託管資源,
這在大多數情況下,足夠了。就是說我們沒有必要去關心內存。但在有的情況下,我們會強制垃圾回收集器在代碼的某個地方運行,釋放內存。這就用到了System.GC.Collect()。System.GC表示一個垃圾收集器。這種情況很少,例如:代碼中大量的對象剛剛停止引用,就適合調用垃圾收集器。
總結
首先堆棧和堆(託管堆)都在進程的虛擬內存中。(在32位處理器上每個進程的虛擬內存爲4GB)
堆棧stack
堆棧中存儲值類型。
堆棧實際上是向下填充,即由高內存地址指向地內存地址填充。
堆棧的工作方式是先分配內存的變量後釋放(先進後出原則)。
堆棧中的變量是從下向上釋放,這樣就保證了堆棧中先進後出的規則不與變量的生命週期起衝突!
堆棧的性能非常高,但是對於所有的變量來說還不太靈活,而且變量的生命週期必須嵌套。
通常我們希望使用一種方法分配內存來存儲數據,並且方法退出後很長一段時間內數據仍然可以使用。此時就要用到堆(託管堆)!
堆(託管堆)heap
堆(託管堆)存儲引用類型。
此堆非彼堆,.NET中的堆由垃圾收集器自動管理。
與堆棧不同,堆是從下往上分配,所以自由的空間都在已用空間的上面。
比如創建一個對象:
Customer cus;
cus = newCustomer();
申明一個Customer的引用cus,在堆棧上給這個引用分配存儲空間。這僅僅只是一個引用,不是實際的Customer對象!
cus佔4個字節的空間,包含了存儲Customer的引用地址。
接着分配堆上的內存以存儲Customer對象的實例,假定Customer對象的實例是32字節,爲了在堆上找到一個存儲Customer對象的存儲位置。.NET運行庫在堆中搜索第一個從未使用的,32字節的連續塊存儲Customer對象的實例!
然後把分配給Customer對象實例的地址賦給cus變量!
從這個例子中可以看出,建立對象引用的過程比建立值變量的過程複雜,且不能避免性能的降低!
實際上就是.NET運行庫保存對的狀態信息,在堆中添加新數據時,堆棧中的引用變量也要更新。
有種機制在分配變量內存的時候,不會受到堆棧的限制:把一個引用變量的值賦給一個相同類型的變量,那麼這兩個變量就引用同一個堆中的對象。

當一個應用變量出作用域時,它會從堆棧中刪除。但引用對象的數據仍然保留在堆中,一直到程序結束 或者 該數據不被任何變量應用時,垃圾收集器會刪除它。


原文出處:http://news.ccidnet.com/art/32855/20100430/2048739_1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章