在32位的Windows操作系統(tǒng)中,,每個(gè)進(jìn)程都可以使用4GB的內(nèi)存,,這得益于虛擬尋址技術(shù),在這4GB的內(nèi)存中存儲(chǔ)著可執(zhí)行代碼,、代碼加載的DLL和程序運(yùn)行的所有變量,,在C#中,虛擬內(nèi)存中有個(gè)兩個(gè)存儲(chǔ)變量的區(qū)域,,一個(gè)稱(chēng)為堆棧,,一個(gè)稱(chēng)為托管堆,托管堆的出現(xiàn)是.NET不同于其他語(yǔ)言的地方,,堆棧存儲(chǔ)值類(lèi)型數(shù)據(jù),,而托管堆存儲(chǔ)引用類(lèi)型如類(lèi)、對(duì)象,,并受垃圾收集器的控制和管理,。在堆棧中,,一旦變量超出使用范圍,其使用的內(nèi)存空間會(huì)被其他變量重新使用,,這時(shí)其空間中存儲(chǔ)的值將被其他變量覆蓋而不復(fù)存在,,但有時(shí)候我們希望這些值仍然存在,這就需要托管堆來(lái)實(shí)現(xiàn),。我們用幾段代碼來(lái)說(shuō)明其工作原理,,假設(shè)已經(jīng)定義了一個(gè)類(lèi)class1:
class1 object1;
object1=new class1();
第一句定義了一個(gè)class1的引用,實(shí)質(zhì)上只是在堆棧中分配了一個(gè)4個(gè)字節(jié)的空間,,它將用來(lái)存府后來(lái)實(shí)例化對(duì)象在托管堆中的地址,,在windows中這需要4個(gè)字節(jié)來(lái)表示內(nèi)存地址。第二句實(shí)例化object1對(duì)象,,實(shí)際上是在托管堆中開(kāi)僻了一個(gè)內(nèi)存空間來(lái)存儲(chǔ)類(lèi)class1的一個(gè)具體對(duì)象,,假設(shè)這個(gè)對(duì)象需要36個(gè)字節(jié),那么object1指向的實(shí)際上是在托管堆一個(gè)大小為36個(gè)字節(jié)的連續(xù)內(nèi)存空間開(kāi)始的地址,。由此也可以看出在C#編譯器中為什么不允許使用未實(shí)例化的對(duì)象,,因?yàn)檫@個(gè)對(duì)象在托管堆中還不存在。當(dāng)對(duì)象不再使用時(shí),,這個(gè)被存儲(chǔ)在堆棧中的引用變量將被刪除,,但是從上述機(jī)制可以看出,,在托管堆中這個(gè)引用指向的對(duì)象仍然存在,,其空間何時(shí)被釋放取決垃圾收集器而不是引用變量失去作用域時(shí)。
在使用電腦的過(guò)程中大家可能都有過(guò)這種經(jīng)驗(yàn):電腦用久了以后程序運(yùn)行會(huì)變得越來(lái)越慢,,其中一個(gè)重要原因就是系統(tǒng)中存在大量?jī)?nèi)存碎片,,就是因?yàn)槌绦蚍磸?fù)在堆棧中創(chuàng)建和釋入變量,久而久之可用變量在內(nèi)存中將不再是連續(xù)的內(nèi)存空間,,為了尋址這些變量也會(huì)增加系統(tǒng)開(kāi)銷(xiāo),。在.net中這種情形將得到很大改善,這是因?yàn)橛辛死占鞯墓ぷ?,垃圾收集器將?huì)壓縮托管堆的內(nèi)存空間,,保證可用變量在一個(gè)連續(xù)的內(nèi)存空間內(nèi),同時(shí)將堆棧中引用變量中的地址改為新的地址,,這將會(huì)帶來(lái)額外的系統(tǒng)開(kāi)銷(xiāo),,但是,其帶來(lái)的好處將會(huì)抵消這種影響,,而另外一個(gè)好處是,,程序員將不再花上大量的心思在內(nèi)在泄露問(wèn)題上。
當(dāng)然,,以C#程序中不僅僅只有引用類(lèi)型的變量,,仍然也存在值類(lèi)型和其他托管堆不能管理的對(duì)象,如果文件名柄、網(wǎng)絡(luò)連接和數(shù)據(jù)庫(kù)連接,,這些變量的釋放仍需要程序員通過(guò)析構(gòu)函數(shù)或IDispose接口來(lái)做,。
另一方面,在某些時(shí)候C#程序也需要追求速度,,比如對(duì)一個(gè)含用大量成員的數(shù)組的操作,,如果仍使用傳統(tǒng)的類(lèi)來(lái)操作,將不會(huì)得到很好的性能,,因?yàn)閿?shù)組在C#中實(shí)際是System.Array的實(shí)例,,會(huì)存儲(chǔ)在托管堆中,這將會(huì)對(duì)運(yùn)算造成大量的額外的操作,,因?yàn)槌死占鞒藭?huì)壓縮托管堆,、更新引用地址、還會(huì)維護(hù)托管堆的信息列表,。所幸的是C#中同樣能夠通過(guò)不安全代碼使用C++程序員通常喜歡的方式來(lái)編碼,,在標(biāo)記為unsafe的代碼塊使用指針,這和在C++中使用指針沒(méi)有什么不同,,變量也是存府在堆棧中,,在這種情況下聲明一個(gè)數(shù)組可以使用stackalloc語(yǔ)法,比如聲明一個(gè)存儲(chǔ)有50個(gè)double類(lèi)型的數(shù)組:
double* pDouble=stackalloc double[50]
stackalloc會(huì)給pDouble數(shù)組在堆棧中分配50個(gè)double類(lèi)型大小的內(nèi)存空間,,可以使用pDouble[0],、*(pDouble+1)這種方式操作數(shù)組,與在C++中一樣,,使用指針時(shí)必須知道自己在做什么,,確保訪問(wèn)的正確的內(nèi)存空間,否則將會(huì)出現(xiàn)無(wú)法預(yù)料的錯(cuò)誤,。
掌握托管堆,、堆棧、垃圾收集器和不安全代碼的工作原理和方式,,將有助于你成為真正的優(yōu)秀C#程序員,。
進(jìn)程中每個(gè)線(xiàn)程都有自己的堆棧,這是一段線(xiàn)程創(chuàng)建時(shí)保留下的地址區(qū)域,。我們的“棧內(nèi)存”即在此,。至于“堆”內(nèi)存,我個(gè)人認(rèn)為在未用new定義時(shí),,堆應(yīng)該就是未“保留”未“提交”的自由空間,,new的功能是在這些自由空間中保留(并提交?)出一個(gè)地址范圍
棧(Stack)是操作系統(tǒng)在建立某個(gè)進(jìn)程時(shí)或者線(xiàn)程(在支持多線(xiàn)程的操作系統(tǒng)中是線(xiàn)程)為這個(gè)線(xiàn)程建立的存儲(chǔ)區(qū)域,,該區(qū)域具有FIFO
FILO的特性,,在編譯的時(shí)候可以指定需要的Stack的大小,。在編程中,例如C/C++中,,所有的局部變量都是從棧中分配內(nèi)存空間,,實(shí)際上也不是什么分配,只是從棧頂向上用就行,,在退出函數(shù)的時(shí)候,,只是修改棧指針就可以把棧中的內(nèi)容銷(xiāo)毀,所以速度最快,。
堆(Heap)是應(yīng)用程序在運(yùn)行的時(shí)候請(qǐng)求操作系統(tǒng)分配給自己內(nèi)存,,一般是申請(qǐng)/給予的過(guò)程,C/C++分別用malloc/New請(qǐng)求分配Heap,,用free/delete銷(xiāo)毀內(nèi)存,。由于從操作系統(tǒng)管理的內(nèi)存分配所以在分配和銷(xiāo)毀時(shí)都要占用時(shí)間,所以用堆的效率低的多,!但是堆的好處是可以做的很大,,C/C++對(duì)分配的Heap是不初始化的。 在Java中除了簡(jiǎn)單類(lèi)型(int,char等)都是在堆中分配內(nèi)存,,這也是程序慢的一個(gè)主要原因,。但是跟C/C++不同,Java中分配Heap內(nèi)存是自動(dòng)初始化的,。在Java中所有的對(duì)象(包括int的wrapper Integer)都是在堆中分配的,,但是這個(gè)對(duì)象的引用卻是在Stack中分配。也就是說(shuō)在建立一個(gè)對(duì)象時(shí)從兩個(gè)地方都分配內(nèi)存,,在Heap中分配的內(nèi)存實(shí)際建立這個(gè)對(duì)象,,而在Stack中分配的內(nèi)存只是一個(gè)指向這個(gè)堆對(duì)象的指針(引用)而已,。 在.NET的所有技術(shù)中,,最具爭(zhēng)議的恐怕是垃圾收集(Garbage Collection,GC)了,。作為.NET框架中一個(gè)重要的部分,,托管堆和垃圾收集機(jī)制對(duì)我們中的大部分人來(lái)說(shuō)是陌生的概念。在這篇文章中將要討論托管堆,,和你將從中得到怎樣的好處,。
為什么要托管堆? .NET框架包含一個(gè)托管堆,,所有的.NET語(yǔ)言在分配引用類(lèi)型對(duì)象時(shí)都要使用它,。像值類(lèi)型這樣的輕量級(jí)對(duì)象始終分配在棧中,但是所有的類(lèi)實(shí)例和數(shù)組都被生成在一個(gè)內(nèi)存池中,,這個(gè)內(nèi)存池就是托管堆,。 垃圾收集器的基本算法很簡(jiǎn)單: ● 將所有的托管內(nèi)存標(biāo)記為垃圾 ● 尋找正被使用的內(nèi)存塊,,并將他們標(biāo)記為有效 ● 釋放所有沒(méi)有被使用的內(nèi)存塊 ● 整理堆以減少碎片 托管堆優(yōu)化 看上去似乎很簡(jiǎn)單,但是垃圾收集器實(shí)際采用的步驟和堆管理系統(tǒng)的其他部分并非微不足道,,其中常常涉及為提高性能而作的優(yōu)化設(shè)計(jì),。舉例來(lái)說(shuō),垃圾收集遍歷整個(gè)內(nèi)存池具有很高的開(kāi)銷(xiāo),。然而,,研究表明大部分在托管堆上分配的對(duì)象只有很短的生存期,因此堆被分成三個(gè)段,,稱(chēng)作generations,。新分配的對(duì)象被放在generation 0中。這個(gè)generation是最先被回收的——在這個(gè)generation中最有可能找到不再使用的內(nèi)存,,由于它的尺寸很?。ㄐ〉阶阋苑胚M(jìn)處理器的L2 cache中),因此在它里面的回收將是最快和最高效的,。 托管堆的另外一種優(yōu)化操作與locality of reference規(guī)則有關(guān),。該規(guī)則表明,一起分配的對(duì)象經(jīng)常被一起使用,。如果對(duì)象們?cè)诙阎形恢煤芫o湊的話(huà),,高速緩存的性能將會(huì)得到提高。由于托管堆的天性,,對(duì)象們總是被分配在連續(xù)的地址上,,托管堆總是保持緊湊,結(jié)果使得對(duì)象們始終彼此靠近,,永遠(yuǎn)不會(huì)分得很遠(yuǎn),。這一點(diǎn)與標(biāo)準(zhǔn)堆提供的非托管代碼形成了鮮明的對(duì)比,在標(biāo)準(zhǔn)堆中,,堆很容易變成碎片,,而且一起分配的對(duì)象經(jīng)常分得很遠(yuǎn)。 還有一種優(yōu)化是與大對(duì)象有關(guān)的,。通常,,大對(duì)象具有很長(zhǎng)的生存期。當(dāng)一個(gè)大對(duì)象在.NET托管堆中產(chǎn)生時(shí),,它被分配在堆的一個(gè)特殊部分中,,這部分堆永遠(yuǎn)不會(huì)被整理。因?yàn)橐苿?dòng)大對(duì)象所帶來(lái)的開(kāi)銷(xiāo)超過(guò)了整理這部分堆所能提高的性能,。 關(guān)于外部資源(External Resources)的問(wèn)題 垃圾收集器能夠有效地管理從托管堆中釋放的資源,,但是資源回收操作只有在內(nèi)存緊張而觸發(fā)一個(gè)回收動(dòng)作時(shí)才執(zhí)行。那么,,類(lèi)是怎樣來(lái)管理像數(shù)據(jù)庫(kù)連接或者窗口句柄這樣有限的資源的呢,?等待,,直到垃圾回收被觸發(fā)之后再清理數(shù)據(jù)庫(kù)連接或者文件句柄并不是一個(gè)好方法,這會(huì)嚴(yán)重降低系統(tǒng)的性能,。 所有擁有外部資源的類(lèi),,在這些資源已經(jīng)不再用到的時(shí)候,都應(yīng)當(dāng)執(zhí)行Close或者Dispose方法,。從Beta2(譯注:本文中所有的Beta2均是指.NET Framework Beta2,,不再特別注明)開(kāi)始,Dispose模式通過(guò)IDisposable接口來(lái)實(shí)現(xiàn),。這將在本文的后續(xù)部分討論,。 需要清理外部資源的類(lèi)還應(yīng)當(dāng)實(shí)現(xiàn)一個(gè)終止操作(finalizer)。在C#中,,創(chuàng)建終止操作的首選方式是在析構(gòu)函數(shù)中實(shí)現(xiàn),,而在Framework層,終止操作的實(shí)現(xiàn)則是通過(guò)重載System.Object.Finalize 方法,。以下兩種實(shí)現(xiàn)終止操作的方法是等效的: ~OverdueBookLocator() { Dispose(false); } 和: public void Finalize() { base.Finalize(); Dispose(false); } 在C#中,,同時(shí)在Finalize方法和析構(gòu)函數(shù)實(shí)現(xiàn)終止操作將會(huì)導(dǎo)致錯(cuò)誤的產(chǎn)生。 除非你有足夠的理由,,否則你不應(yīng)該創(chuàng)建析構(gòu)函數(shù)或者Finalize方法,。終止操作會(huì)降低系統(tǒng)的性能,并且增加執(zhí)行期的內(nèi)存開(kāi)銷(xiāo),。同時(shí),,由于終止操作被執(zhí)行的方式,你并不能保證何時(shí)一個(gè)終止操作會(huì)被執(zhí)行,。 內(nèi)存分配和垃圾回收的細(xì)節(jié) 對(duì)GC有了一個(gè)總體印象之后,,讓我們來(lái)討論關(guān)于托管堆中的分配與回收工作的細(xì)節(jié)。托管堆看起來(lái)與我們已經(jīng)熟悉的C++編程中的傳統(tǒng)的堆一點(diǎn)都不像,。在傳統(tǒng)的堆中,,數(shù)據(jù)結(jié)構(gòu)習(xí)慣于使用大塊的空閑內(nèi)存。在其中查找特定大小的內(nèi)存塊是一件很耗時(shí)的工作,,尤其是當(dāng)內(nèi)存中充滿(mǎn)碎片的時(shí)候,。與此不同,在托管堆中,,內(nèi)存被組制成連續(xù)的數(shù)組,指針總是巡著已經(jīng)被使用的內(nèi)存和未被使用的內(nèi)存之間的邊界移動(dòng),。當(dāng)內(nèi)存被分配的時(shí)候,,指針只是簡(jiǎn)單地遞增——由此而來(lái)的一個(gè)好處是,分配操作的效率得到了很大的提升,。 當(dāng)對(duì)象被分配的時(shí)候,,它們一開(kāi)始被放在generation 0中,。當(dāng)generation 0的大小快要達(dá)到它的上限的時(shí)候,一個(gè)只在generation 0中執(zhí)行的回收操作被觸發(fā),。由于generation 0的大小很小,,因此這將是一個(gè)非常快的GC過(guò)程,。這個(gè)GC過(guò)程的結(jié)果是將generation 0徹底的刷新了一遍,。不再使用的對(duì)象被釋放,確實(shí)正被使用的對(duì)象被整理并移入generation 1中,。 當(dāng)generation 1的大小隨著從generation 0中移入的對(duì)象數(shù)量的增加而接近它的上限的時(shí)候,,一個(gè)回收動(dòng)作被觸發(fā)來(lái)在generation 0和generation 1中執(zhí)行GC過(guò)程。如同在generation 0中一樣,,不再使用的對(duì)象被釋放,,正在被使用的對(duì)象被整理并移入下一個(gè)generation中。大部分GC過(guò)程的主要目標(biāo)是generation 0,,因?yàn)樵趃eneration 0中最有可能存在大量的已不再使用的臨時(shí)對(duì)象,。對(duì)generation 2的回收過(guò)程具有很高的開(kāi)銷(xiāo),并且此過(guò)程只有在generation 0和generation 1的GC過(guò)程不能釋放足夠的內(nèi)存時(shí)才會(huì)被觸發(fā),。如果對(duì)generation 2的GC過(guò)程仍然不能釋放足夠的內(nèi)存,,那么系統(tǒng)就會(huì)拋出OutOfMemoryException異常 帶有終止操作的對(duì)象的垃圾收集過(guò)程要稍微復(fù)雜一些。當(dāng)一個(gè)帶有終止操作的對(duì)象被標(biāo)記為垃圾時(shí),,它并不會(huì)被立即釋放,。相反,它會(huì)被放置在一個(gè)終止隊(duì)列(finalization queue)中,,此隊(duì)列為這個(gè)對(duì)象建立一個(gè)引用,,來(lái)避免這個(gè)對(duì)象被回收。后臺(tái)線(xiàn)程為隊(duì)列中的每個(gè)對(duì)象執(zhí)行它們各自的終止操作,,并且將已經(jīng)執(zhí)行過(guò)終止操作的對(duì)象從終止隊(duì)列中刪除,。只有那些已經(jīng)執(zhí)行過(guò)終止操作的對(duì)象才會(huì)在下一次垃圾回收過(guò)程中被從內(nèi)存中刪除。這樣做的一個(gè)后果是,,等待被終止的對(duì)象有可能在它被清除之前,,被移入更高一級(jí)的generation中,從而增加它被清除的延遲時(shí)間,。 需要執(zhí)行終止操作的對(duì)象應(yīng)當(dāng)實(shí)現(xiàn)IDisposable接口,,以便客戶(hù)程序通過(guò)此接口快速執(zhí)行終止動(dòng)作。IDisposable接口包含一個(gè)方法——Dispose,。這個(gè)被Beta2引入的接口,,采用一種在Beta2之前就已經(jīng)被廣泛使用的模式實(shí)現(xiàn)。從本質(zhì)上講,,一個(gè)需要終止操作的對(duì)象暴露出Dispose方法,。這個(gè)方法被用來(lái)釋放外部資源并抑制終止操作,,就象下面這個(gè)程序片斷所演示的那樣: view plaincopy to clipboardprint? public class OverdueBookLocator: IDisposable { ~OverdueBookLocator() { InternalDispose(false); } public void Dispose() { InternalDispose(true); } protected void InternalDispose(bool disposing) { if(disposing) { GC.SuppressFinalize(this); // Dispose of managed objects if disposing. } // free external resources here } } 本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/asdfnike/archive/2009/04/07/4054568.aspx |
|
來(lái)自: TivonStone > 《技術(shù)摘要》