轉帖|其它|編輯:郝浩|2009-04-01 10:18:19.000|閱讀 413 次
概述:C#編程的一個優(yōu)點是程序員不需要關心具體的內存管理,尤其是垃圾收集器會處理所有的內存清理工作。雖然不必手工管理內存,但如果要編寫高質量的代碼,還是要理解后臺發(fā)生的事情,理解C#的內存管理。本文主要介紹給變量分配內存時計算機內存中發(fā)生的情況。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
C#編程的一個優(yōu)點是程序員不需要關心具體的內存管理,尤其是垃圾收集器會處理所有的內存清理工作。雖然不必手工管理內存,但如果要編寫高質量的代碼,還是要理解后臺發(fā)生的事情,理解C#的內存管理。本文主要介紹給變量分配內存時計算機內存中發(fā)生的情況。
C#將數據分為兩種:值數據類型和引用數據類型,這兩種數據類型存儲在內存中的不同的地方:值數據類型存儲在堆棧中,而引用類型存儲在內存的托管堆中。
1、內存簡介
Windows使用一個系統:虛擬尋址系統。這個系統的作用是將程序可用的內存地址映射到硬件內存中的實際地址上。其實際結果就是32位的機子上每個進程都可以使用4GB的內存,當然,64位機這個數字就大了去了。這4GB的內存實際上包含了程序的所有的部分:可執(zhí)行代碼,DLL以及程序運行時使用的所有變量的內容。這個4GB的內存成為虛擬地址空間或虛擬內存。為方便,這里成為內存。
4GB中的每個存儲單元都是從零開始向上存儲的。要訪問存儲在內存中的某個空間中的值,就必須提供表示該存儲單元的一個數字。在高級編程語言中,編譯器的一個重要作用就是負責將人們可以理解的變量名稱變?yōu)樘幚砥骺梢岳斫獾膬却娴刂贰?br />
2、堆棧
在內存中,有一個區(qū)域成為堆棧,存儲對象
對象成員的值數據類型調用方法時,傳遞給所有方法的參數的副本注意:調用方法時,堆棧存儲的是所有參數的副本,因此,經值類型A傳遞給函數,A的值是不會變化的。當然,引用類型是會變化的,因為在堆棧中存儲的是引用類型的地址,這在后面會有詳細的介紹。
下面以一個例子來說明堆棧的工作方式,如下面的代碼:
{
int a;
//do something;
{
int b;
//do something
}
}
首先聲明a,在內部的代碼塊中聲明b,然后內部的代碼塊終止,b就出了作用域,最后a出作用域。所以b的生命周期總是包含在a的生命周期內,在釋放變量的時候,其順序總是和分配內存的順序是相反的。即:變量的生存周期都是嵌套的。這就是堆棧的工作方式。
3、托管堆
堆棧具有相當高的性能,但是變量的生命周期必須是嵌套的,這個要求在有的時候過于苛刻。我們希望有一種別的方法來分配內存,存儲一些數據,并在方法退出的很長一段時間內,這些數據仍然是可用的,這時,就使用托管堆。
托管堆(簡稱堆)是內存中的另外一個區(qū)域,我們仍然用一個例子來說明堆的工作方式,如下面代碼:
{
Customer customer1;
customer1=new Customer();
Customer customer2=new Customer();
//do something
}
首先,聲明一個Customer:customer1,在堆棧上給這個引用分配存儲控件。請注意:僅僅是給這個引用分配存儲空間,并不是實際的Customer對象。customer1占用4個字節(jié)的空間(32位機),來表示Customer對象在內存中的地址。
然后,執(zhí)行第二行代碼,完成以下操作:
在堆上分配存儲空間,用來存儲Customer對象,注意:這里是Customer對像。
將變量customer1的值設為分配給Customer對象的內存地址從這個例子中可以看出,建立引用類型的變量的過程要比獎勵值類型變量的過程復雜,且不避免的有性能的降低。但是,我們可以將一個引用變量的值賦給另一個引用變量,當一個變量出作用域時,它會從堆棧中刪除,但是對象的數據仍然保留在內存中,直到程序停止。
這樣,我們在將一個引用變量A傳遞給函數時,僅僅是將變量A的引用傳遞給了函數,即:僅僅是在堆棧上分配內存,即變量B兩者指向同一個內存地址。因此,當變量B發(fā)生變化時,變量A也會發(fā)生變化。
4、裝箱和拆箱
裝箱和拆箱就是值類型和引用類型的項目轉化,裝箱可以將值類型轉化為引用類型,拆箱的作用正好相反,經引用類型轉化為值類型。
5、垃圾收集
一般情況下。NET運行庫會在認為需要的時候運行垃圾收集器來釋放托管資源,
這在大多數情況下,足夠了。就是說我們沒有必要去關心內存。但在有的情況下,我們會強制垃圾回收集器在代碼的某個地方運行,釋放內存。這就用到了System.GC.Collect()。System.GC表示一個垃圾收集器。這種情況很少,例如:代碼中大量的對象剛剛停止引用,就適合調用垃圾收集器。
總結
首先堆棧和堆(托管堆)都在進程的虛擬內存中。(在32位處理器上每個進程的虛擬內存為4GB)
堆棧stack
堆棧中存儲值類型。
堆棧實際上是向下填充,即由高內存地址指向地內存地址填充。
堆棧的工作方式是先分配內存的變量后釋放(先進后出原則)。
堆棧中的變量是從下向上釋放,這樣就保證了堆棧中先進后出的規(guī)則不與變量的生命周期起沖突!
堆棧的性能非常高,但是對于所有的變量來說還不太靈活,而且變量的生命周期必須嵌套。
通常我們希望使用一種方法分配內存來存儲數據,并且方法退出后很長一段時間內數據仍然可以使用。此時就要用到堆(托管堆)!
堆(托管堆)heap
堆(托管堆)存儲引用類型。
此堆非彼堆,.NET中的堆由垃圾收集器自動管理。
與堆棧不同,堆是從下往上分配,所以自由的空間都在已用空間的上面。
比如創(chuàng)建一個對象:
Customer cus;
cus = new Customer();
申明一個Customer的引用cus,在堆棧上給這個引用分配存儲空間。這僅僅只是一個引用,不是實際的Customer對象!
cus占4個字節(jié)的空間,包含了存儲Customer的引用地址。
接著分配堆上的內存以存儲Customer對象的實例,假定Customer對象的實例是32字節(jié),為了在堆上找到一個存儲Customer對象的存儲位置。
.NET運行庫在堆中搜索第一個從未使用的,32字節(jié)的連續(xù)塊存儲Customer對象的實例!
然后把分配給Customer對象實例的地址賦給cus變量!
從這個例子中可以看出,建立對象引用的過程比建立值變量的過程復雜,且不能避免性能的降低!
實際上就是.NET運行庫保存對的狀態(tài)信息,在堆中添加新數據時,堆棧中的引用變量也要更新。
性能上損失很多!
有種機制在分配變量內存的時候,不會受到堆棧的限制:把一個引用變量的值賦給一個相同類型的變量,那么這兩個變量就引用同一個堆中的對象。
當一個應用變量出作用域時,它會從堆棧中刪除。但引用對象的數據仍然保留在堆中,一直到程序結束 或者 該數據不被任何變量應用時,垃圾收集器會刪除它。
本站文章除注明轉載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉載自:博客園