C#的內(nèi)存管理:堆、棧、托管堆與指針_第1頁
C#的內(nèi)存管理:堆、棧、托管堆與指針_第2頁
C#的內(nèi)存管理:堆、棧、托管堆與指針_第3頁
C#的內(nèi)存管理:堆、棧、托管堆與指針_第4頁
C#的內(nèi)存管理:堆、棧、托管堆與指針_第5頁
已閱讀5頁,還剩2頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、c#的內(nèi)存管理:堆、棧、托管堆與指針在 32 位的 windows操作系統(tǒng)中,每個進程都可以使用4gb 的內(nèi)存,這得益于虛擬尋址技術(shù),在這4gb 的內(nèi)存中存儲著可執(zhí)行代碼、代碼加載的dll 和程序運行的所有變量,在c#中,虛擬內(nèi)存中有個兩個存儲變量的區(qū)域,一個稱為堆棧,一個稱為托管堆,托管堆的出現(xiàn)是 .net 不同于其他語言的地方,堆棧存儲值類型數(shù)據(jù),而托管堆存儲引用類型如類、對象,并受垃圾收集器的控制和管理。在堆棧中,一旦變量超出使用范圍,其使用的內(nèi)存空間會被其他變量重新使用,這時其空間中存儲的值將被其他變量覆蓋而不復存在,但有時候我們希望這些值仍然存在,這就需要托管堆來實現(xiàn)。我們用幾段代碼

2、來說明其工作原理,假設已經(jīng)定義了一個類class1: public class class1 public void mymethod() public string myproperty get return perty class1 object1; object1=new class1();第一句定義了一個class1 的引用,實質(zhì)上只是在堆棧中分配了一個4 個字節(jié)的空間,它將用來存府后來實例化對象在托管堆中的地址,在windows 中這需要4 個字節(jié)來表示內(nèi)存地址。第二句實例化object1 對象, 實際上是在托管堆中開僻了一個內(nèi)存空間來存儲類class1的一個具體

3、對象,假設這個對象需要36 個字節(jié),那么object1 指向的實際上是在托管堆一個大小為36 個字節(jié)的連續(xù)內(nèi)存空間開始的地址。由此也可以看出在c#編譯器中為什么不允許使用未實例化的對象,因為這個對象在托管堆中還不存在。當對象不再使用時,這個被存儲在堆棧中的引用變量將被刪除,但是從上述機制可以看出,在托管堆中這個引用指向的對象仍然存在,其空間何時被釋放取決垃圾收集器而不是引用變量失去作用域時。在使用電腦的過程中大家可能都有過這種經(jīng)驗:電腦用久了以后程序運行會變得越來越慢,其中一個重要原因就是系統(tǒng)中存在大量內(nèi)存碎片,就是因為程序反復在堆棧中創(chuàng)建和釋入變量,久而久之可用變量在內(nèi)存中將不再是連續(xù)的內(nèi)存

4、空間,為了尋址這些變量也會增加系統(tǒng)開銷。 在.net 中這種情形將得到很大改善,這是因為有了垃圾收集器的工作,垃圾收集器將會壓縮托管堆的內(nèi)存空間,保證可用變量在一個連續(xù)的內(nèi)存空間內(nèi),同時將堆棧中引用變量中的地址改為新的地址,這將會帶來額外的系統(tǒng)開銷,但是, 其帶來的好處將會抵消這種影響,而另外一個好處是,程序員將不再花上大量的心思在內(nèi)在泄露問題上。當然,以 c# 程序中不僅僅只有引用類型的變量,仍然也存在值類型和其他托管堆不能管理的對象, 如果文件名柄、 網(wǎng)絡連接和數(shù)據(jù)庫連接,這些變量的釋放仍需要程序員通過析構(gòu)函數(shù)或 idispose 接口來做。另一方面,在某些時候c#程序也需要追求速度,比如

5、對一個含用大量成員的數(shù)組的操作,如果仍使用傳統(tǒng)的類來操作,將不會得到很好的性能,因為數(shù)組在c#中實際是 system.array的實例, 會存儲在托管堆中,這將會對運算造成大量的額外的操作,因為除了垃圾收集器除了會壓縮托管堆、更新引用地址、還會維護托管堆的信息列表。所幸的是c#中同樣能夠通過不安全代碼使用c+ 程序員通常喜歡的方式來編碼,在標記為 unsafe 的代碼塊使用指針,這和在 c+ 中使用指針沒有什么不同,變量也是存府在堆棧中,在這種情況下聲明一個數(shù)組可以使用stackalloc語法,比如聲明一個存儲有50 個 double 類型的數(shù)組:double* pdouble=stackal

6、loc double50;stackalloc會給 pdouble數(shù)組在堆棧中分配50 個 double 類型大小的內(nèi)存空間,可以使用pdouble0 、*(pdouble+1) 這種方式操作數(shù)組,與在c+ 中一樣,使用指針時必須知道自己在做什么,確保訪問的正確的內(nèi)存空間,否則將會出現(xiàn)無法預料的錯誤。掌握托管堆、 堆棧、 垃圾收集器和不安全代碼的工作原理和方式,將有助于你成為真正的優(yōu)秀 c#程序員。進程中每個線程都有自己的堆棧,這是一段線程創(chuàng)建時保留下的地址區(qū)域。我們的“ 棧內(nèi)存 ”即在此。至于 “ 堆” 內(nèi)存,我個人認為在未用new 定義時,堆應該就是未“ 保留 ” 未“ 提交 ” 的自由空

7、間, new 的功能是在這些自由空間中保留(并提交?)出一個地址范圍棧(stack) 是操作系統(tǒng)在建立某個進程時或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個線程建立的存儲區(qū)域,該區(qū)域具有fifo 的特性,在編譯的時候可以指定需要的stack的大小。在編程中,例如c/c+ 中,所有的局部變量都是從棧中分配內(nèi)存空間,實際上也不是什么分配, 只是從棧頂向上用就行,在退出函數(shù)的時候,只是修改棧指針就可以把棧中的內(nèi)容銷毀,所以速度最快。堆 (heap) 是應用程序在運行的時候請求操作系統(tǒng)分配給自己內(nèi)存,一般是申請 /給予的過程,c/c+ 分別用 malloc/new請求分配 heap ,用free/

8、delete銷毀內(nèi)存。由于從操作系統(tǒng)管理的內(nèi)存分配所以在分配和銷毀時都要占用時間,所以用堆的效率低的多!但是堆的好處是可以做的很大, c /c+ 對分配的heap 是不初始化的。在 java 中除了簡單類型(int,char 等)都是在堆中分配內(nèi)存,這也是程序慢的一個主要原因。 但是跟 c/c+ 不同,java 中分配 heap 內(nèi)存是自動初始化的。在 java 中所有的對象 (包括 int 的 wrapper integer ) 都是在堆中分配的, 但是這個對象的引用卻是在stack 中分配。也就是說在建立一個對象時從兩個地方都分配內(nèi)存,在 heap 中分配的內(nèi)存實際建立這個對象,而在 st

9、ack 中分配的內(nèi)存只是一個指向這個堆對象的指針(引用)而已。在.net 的所有技術(shù)中, 最具爭議的恐怕是垃圾收集(garbage collection, gc) 了。作為 .net框架中一個重要的部分,托管堆和垃圾收集機制對我們中的大部分人來說是陌生的概念。在這篇文章中將要討論托管堆,和你將從中得到怎樣的好處。為什么要托管堆?.net 框架包含一個托管堆,所有的.net 語言在分配引用類型對象時都要使用它。像值類型這樣的輕量級對象始終分配在棧中,但是所有的類實例和數(shù)組都被生成在一個內(nèi)存池中,這個內(nèi)存池就是托管堆。垃圾收集器的基本算法很簡單: 將所有的托管內(nèi)存標記為垃圾 尋找正被使用的內(nèi)存塊,

10、并將他們標記為有效 釋放所有沒有被使用的內(nèi)存塊 整理堆以減少碎片托管堆優(yōu)化看上去似乎很簡單, 但是垃圾收集器實際采用的步驟和堆管理系統(tǒng)的其他部分并非微不足道,其中常常涉及為提高性能而作的優(yōu)化設計。舉例來說, 垃圾收集遍歷整個內(nèi)存池具有很高的開銷。 然而,研究表明大部分在托管堆上分配的對象只有很短的生存期,因此堆被分成三個段,稱作 generations 。新分配的對象被放在generation 0中。這個 generation是最先被回收的 在這個 generation中最有可能找到不再使用的內(nèi)存,由于它的尺寸很?。ㄐ〉阶阋苑胚M處理器的l2 cache中),因此在它里面的回收將是最快和最高效的

11、。托管堆的另外一種優(yōu)化操作與locality of reference規(guī)則有關(guān)。該規(guī)則表明,一起分配的對象經(jīng)常被一起使用。如果對象們在堆中位置很緊湊的話,高速緩存的性能將會得到提高。由于托管堆的天性,對象們總是被分配在連續(xù)的地址上,托管堆總是保持緊湊,結(jié)果使得對象們始終彼此靠近, 永遠不會分得很遠。 這一點與標準堆提供的非托管代碼形成了鮮明的對比,在標準堆中,堆很容易變成碎片,而且一起分配的對象經(jīng)常分得很遠。還有一種優(yōu)化是與大對象有關(guān)的。通常,大對象具有很長的生存期。當一個大對象在.net托管堆中產(chǎn)生時,它被分配在堆的一個特殊部分中,這部分堆永遠不會被整理。因為移動大對象所帶來的開銷超過了整理

12、這部分堆所能提高的性能。關(guān)于外部資源(external resources)的問題垃圾收集器能夠有效地管理從托管堆中釋放的資源,但是資源回收操作只有在內(nèi)存緊張而觸發(fā)一個回收動作時才執(zhí)行。那么,類是怎樣來管理像數(shù)據(jù)庫連接或者窗口句柄這樣有限的資源的呢?等待, 直到垃圾回收被觸發(fā)之后再清理數(shù)據(jù)庫連接或者文件句柄并不是一個好方法,這會嚴重降低系統(tǒng)的性能。所有擁有外部資源的類,在這些資源已經(jīng)不再用到的時候,都應當執(zhí)行close 或者 dispose方法。從 beta2 (譯注:本文中所有的beta2 均是指 .net framework beta2,不再特別注明)開始, dispose 模式通過idi

13、sposable接口來實現(xiàn)。這將在本文的后續(xù)部分討論。需要清理外部資源的類還應當實現(xiàn)一個終止操作(finalizer )。在 c#中,創(chuàng)建終止操作的首選方式是在析構(gòu)函數(shù)中實現(xiàn),而在framework 層,終止操作的實現(xiàn)則是通過重載system.object.finalize 方法。以下兩種實現(xiàn)終止操作的方法是等效的:overduebooklocator() dispose(false); public void finalize() base.finalize(); dispose(false); 在 c#中,同時在finalize 方法和析構(gòu)函數(shù)實現(xiàn)終止操作將會導致錯誤的產(chǎn)生。除非你有足夠的

14、理由,否則你不應該創(chuàng)建析構(gòu)函數(shù)或者finalize 方法。終止操作會降低系統(tǒng)的性能,并且增加執(zhí)行期的內(nèi)存開銷。同時,由于終止操作被執(zhí)行的方式,你并不能保證何時一個終止操作會被執(zhí)行。內(nèi)存分配和垃圾回收的細節(jié)對 gc 有了一個總體印象之后,讓我們來討論關(guān)于托管堆中的分配與回收工作的細節(jié)。托管堆看起來與我們已經(jīng)熟悉的c+編程中的傳統(tǒng)的堆一點都不像。在傳統(tǒng)的堆中,數(shù)據(jù)結(jié)構(gòu)習慣于使用大塊的空閑內(nèi)存。在其中查找特定大小的內(nèi)存塊是一件很耗時的工作,尤其是當內(nèi)存中充滿碎片的時候。與此不同, 在托管堆中, 內(nèi)存被組制成連續(xù)的數(shù)組,指針總是巡著已經(jīng)被使用的內(nèi)存和未被使用的內(nèi)存之間的邊界移動。當內(nèi)存被分配的時候,指

15、針只是簡單地遞增 由此而來的一個好處是,分配操作的效率得到了很大的提升。當對象被分配的時候,它們一開始被放在generation 0中。當 generation 0的大小快要達到它的上限的時候,一個只在generation 0中執(zhí)行的回收操作被觸發(fā)。由于generation 0的大小很小,因此這將是一個非??斓膅c 過程。這個gc 過程的結(jié)果是將generation 0徹底的刷新了一遍。不再使用的對象被釋放,確實正被使用的對象被整理并移入generation 1 中。當 generation 1的大小隨著從generation 0中移入的對象數(shù)量的增加而接近它的上限的時候,一個回收動作被觸發(fā)來

16、在generation 0和 generation 1中執(zhí)行 gc 過程。如同在generation 0中一樣,不再使用的對象被釋放,正在被使用的對象被整理并移入下一個generation中。大部分gc 過程的主要目標是generation 0 ,因為在 generation 0中最有可能存在大量的已不再使用的臨時對象。對generation 2的回收過程具有很高的開銷,并且此過程只有在generation 0和 generation 1的 gc 過程不能釋放足夠的內(nèi)存時才會被觸發(fā)。如果對 generation 2的 gc 過程仍然不能釋放足夠的內(nèi)存,那么系統(tǒng)就會拋出outofmemoryex

17、ception異常帶有終止操作的對象的垃圾收集過程要稍微復雜一些。當一個帶有終止操作的對象被標記為垃圾時,它并不會被立即釋放。相反,它會被放置在一個終止隊列(finalization queue)中,此隊列為這個對象建立一個引用,來避免這個對象被回收。后臺線程為隊列中的每個對象執(zhí)行它們各自的終止操作,并且將已經(jīng)執(zhí)行過終止操作的對象從終止隊列中刪除。只有那些已經(jīng)執(zhí)行過終止操作的對象才會在下一次垃圾回收過程中被從內(nèi)存中刪除。這樣做的一個后果是,等待被終止的對象有可能在它被清除之前,被移入更高一級的generation中,從而增加它被清除的延遲時間。需要執(zhí)行終止操作的對象應當實現(xiàn)idisposabl

18、e接口, 以便客戶程序通過此接口快速執(zhí)行終止動作。 idisposable接口包含一個方法 dispose 。這個被beta2 引入的接口,采用一種在 beta2 之前就已經(jīng)被廣泛使用的模式實現(xiàn)。從本質(zhì)上講,一個需要終止操作的對象暴露出dispose 方法。這個方法被用來釋放外部資源并抑制終止操作,就象下面這個程序片斷所演示的那樣:public class overduebooklocator: idisposable overduebooklocator() internaldispose(false); public void dispose() internaldispose(true)

19、; protected void internaldispose(bool disposing) if(disposing) gc.suppressfinalize(this); / dispose of managed objects if disposing. / free external resources here 這些都是 .net 中 clr 的概念,和c#沒多大關(guān)系。使用基于 clr 的語言編譯器開發(fā)的代碼稱為托管代碼。托管堆是 clr 中自動內(nèi)存管理的基礎(chǔ)。初始化新進程時,運行時會為進程保留一個連續(xù)的地址空間區(qū)域。 這個保留的地址空間被稱為托管堆。托管堆維護著一個指針,用它指

20、向?qū)⒃诙阎蟹峙涞南乱粋€對象的地址。最初,該指針設置為指向托管堆的基址。認真看 msdn library ,就會搞清楚這些概念。以下代碼說明的很形象:/ 引用類型 (class 類類型 ) class someref public int32 x; / 值類型 (struct) struct someval(pulic int32 x; static void valuetypedemo() someref r1=new someref();/分配在托管堆someval v1=new someval();/堆棧上r1.x=5;/解析指針v1.x=5;/在堆棧上修改someref r2=r1;/僅

21、拷貝引用 (指針 ) someval v2=v1;/先在堆棧上分配,然后拷貝成員r1.x=8;/改變了 r1,r2的值v1.x=9;/改變了 v1, 沒有改變 v2 定義一個范圍,將在此范圍之外釋放一個或多個對象。using (font font1 = new font(arial, 10.0f) /do somthing. c# 通過 .net framework 公共語言運行庫(clr) 來自動釋放用來存儲不再需要的對象的內(nèi)存。內(nèi)存的釋放具有不確定性;一旦clr 決定執(zhí)行垃圾回收,就會釋放內(nèi)存。但是,通常最好盡快釋放諸如文件句柄和網(wǎng)絡連接這樣的有限資源。using 語句允許程序員指定使用資源的對象應當何時釋放資源。為using 語句提供的對象必須實現(xiàn)idisposable 接口。此接口提供了dispose 方法,該方法將釋放此對象

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論