理解垃圾回收平臺的基本工作原理
每個程序都有使用不同的資源,比如文件、內(nèi)存緩沖、屏幕空間、網(wǎng)絡(luò)連接、數(shù)據(jù)庫連接等,其實(shí)在面上對象的程序設(shè)計(jì)中,每個類型都代表可供程序使用的一種資源,
要使用這些資源就必須為這些資源類型分配內(nèi)存。下面是訪問一種資源所需的步驟:
(1) 調(diào)用IL指令的newobj,為代表資源的類型分配內(nèi)存,在c#中使用new操作符,編譯器就會自動生成該指令。
(2) 初始化資源,設(shè)置資源的初始狀態(tài),使資源可用。類型的實(shí)例構(gòu)造器負(fù)責(zé)這個工作。
(3) 訪問類型成員來使用資源。
(4) 摧毀資源狀態(tài)以進(jìn)行清理。
(5) 釋放內(nèi)存。垃圾回收器負(fù)責(zé)這一步。
看是如此簡單的過程,有多少程序員忘記釋放不再需要的內(nèi)存?又有多少程序員試圖調(diào)用已釋放的內(nèi)存?
進(jìn)行非托管編程時,應(yīng)用程序的這兩種bug比其它bug嚴(yán)重很多,一般都無法預(yù)測到他們的后果,如果是其它bug,可以發(fā)現(xiàn)程序行為異常來修正。但是這兩種bug會造成資源泄露和對象損壞,是應(yīng)用程序表現(xiàn)不可預(yù)測。
通常進(jìn)行資源管理很難而且很枯燥。它會極大分散開發(fā)人員的注意力,使之無法專注要真正解決的問題。要是有一種機(jī)制能為開發(fā)人員簡化這個工作,那是一件不錯的事情,幸好有這中機(jī)制,這就是垃圾回收(簡稱GC –garbage collection).
有了GC使的開發(fā)人員的到解放,就不必跟蹤內(nèi)存的使用,也不必知道什么時候釋放內(nèi)存。但是垃圾回收對內(nèi)存中的類型資源一無所知。這意味著GC不知道怎么執(zhí)行前面的第四步:摧毀資源狀態(tài)以進(jìn)行清理 。為了使資源得到正確的清理,開發(fā)人員必須編寫知道如何正確清理資源的代碼。這些代碼通常放在Finalize,Dispose和close方法中,但是GC提供了一些幫助,使得開發(fā)人員大多數(shù)時候可以跳過第四步。
另外要注意,值類型、集合類型、String、Attribute、Delegate和Exception所代表的資源無需進(jìn)行特殊清理。
如果一個類型代表非托管和本地資源(比如文件、數(shù)據(jù)庫連接、套接字、mutex、位圖和圖標(biāo)等),那么在對象準(zhǔn)備回收時,必須進(jìn)行一些清理代碼。
2、從托管堆中分配資源
CLR要求所有托管代碼的資源在托管堆中分配。這個堆和C運(yùn)行時的堆非常相似,只是你永遠(yuǎn)不要從托管堆中刪除對象(應(yīng)用程序不需要的對象會自動刪除),這就引出一個問題:托管堆如何知道應(yīng)用程序不需要一個對象?
目前有好多種垃圾回收器的算法,不同算法都針對不同環(huán)境進(jìn)行優(yōu)化,已提供這種環(huán)境下最佳的性能。本系列將關(guān)注Microsoft.NET Framework的CLR所采用的垃圾回收算法。
前面說到new負(fù)責(zé)創(chuàng)建一個對象,導(dǎo)致編譯器生成一個newobj指令。該指令會導(dǎo)致CLR執(zhí)行以下步驟。
(1) 計(jì)算類型(以及所有基類型)資源所需要的字節(jié)數(shù)。
(2) 加上對象開銷所需的字節(jié)數(shù)。每個對象都有兩個字節(jié)的開銷:一個是類型對象指針和一個同步塊索引(可能不同資料上面名字翻譯不一樣,以后章節(jié)中再對這兩種不同的開銷做講解,你也可以去MSDN網(wǎng)站上查一些資料),真對32位應(yīng)用程序和64位應(yīng)用程序分配的字節(jié)是不一樣的。
(3) CLR檢查保留區(qū)域是否能提供分配對象所需的字節(jié)數(shù),如果托管堆有足夠的空間對象會被放進(jìn)去,這里有個NextObjPtr指針,該對象是在這個指針指向的地址放入的。并且為他分配的字節(jié)數(shù)就會清零,接著調(diào)用類型實(shí)例構(gòu)造器,IL指令newobj會返回對象的地址。在返回地址之前,NextObjPtr指針的值會加上對象占據(jù)的字節(jié)數(shù),這樣會得到一個新值,他指向下一個對象放入托管堆得地址。
為了對比,讓我們看一下C運(yùn)行時堆如何分配。在C運(yùn)行時堆中,為對象分配內(nèi)存要遍歷一個數(shù)據(jù)結(jié)構(gòu)組成的鏈表。一旦發(fā)現(xiàn)有足夠大的塊,那個塊就會被拆分,同時修改鏈表節(jié)點(diǎn)的指針,以確保鏈表的完整性。對于托管堆,分配對象只需要在指針上加一個值,這個顯然明顯快得多。事實(shí)上,托管堆分配對象的速度幾乎可以與從線程棧分配內(nèi)存媲美。另外,大多數(shù)堆(包含C運(yùn)行時堆)都是在他們找到可用空間的地方分配,所以連續(xù)創(chuàng)建幾個對象,有可能被分散。中間相隔數(shù)MB的地址空間。但是在托管堆中連續(xù)分配對象能夠確保他們對象的地址是連續(xù)的。
綜合上訴,似乎托管堆得實(shí)現(xiàn)的簡單行和速度方面遠(yuǎn)遠(yuǎn)優(yōu)于C運(yùn)行時堆。但是這有個特別注意的細(xì)節(jié)你的搞清楚,托管堆之所以有這么多好處,是因?yàn)樗隽艘粋€相當(dāng)大膽的假設(shè):“地址空間和存儲是無限的”。這個假設(shè)簡直荒謬之極。所以托管堆必須通過某種機(jī)制來允許它這么假設(shè)。這種機(jī)制就是垃圾回收。
應(yīng)用程序調(diào)用new創(chuàng)建對象時,可能沒有足夠的地址空間來分配該對象,托管堆將需要的字節(jié)加到NextObjPtr指針中的地址上來檢查這種情況。如果結(jié)果值超過了地址的末尾,表明托管堆已滿,必須進(jìn)行垃圾回收了。
備注:本系列文章來自<<CLR via C#>>第三版
新聞熱點(diǎn)
疑難解答
圖片精選