第三章:GC Heap管理
這是《設(shè)計(jì).Net Compact Framework CLR》的第三部分。在前面兩章中,我們討論了CLR如何管理內(nèi)存和JIT編譯器的基本設(shè)計(jì)原則。
Part I, Overview and Background
Part II, Jit Compiler Design Considerations
這一章我們主要討論垃圾收集設(shè)計(jì)中如何管理GC heap的問(wèn)題。
---------
討論.NET平臺(tái)如何管理內(nèi)存,垃圾收集肯定是第一個(gè)被提及的話題。不必驚訝,Compact Framework的垃圾收集和桌面版本有很多地方不同,原因是運(yùn)行環(huán)境的不同。就像Compact Framework CLR中的其他大部分子系統(tǒng)一樣,垃圾收集被設(shè)計(jì)成,在設(shè)備可用內(nèi)存較低時(shí),釋放所有能夠釋放的內(nèi)存。
對(duì)更高層次,GC提供了兩個(gè)基本功能:分配內(nèi)存保存引用類型的實(shí)例,在這些實(shí)例不再使用時(shí)收集他們。
Allocating Reference Types
通過(guò)第一章的討論,我們知道GC heap存在于進(jìn)程獨(dú)有的32M虛擬地址空間中。和JIT堆一樣,GC heap開(kāi)始時(shí)很小,隨著增長(zhǎng)需要更多的空間。然而,比較JIT堆,GC heap的增長(zhǎng)有兩個(gè)重要的不同:GC heap以一個(gè)固定的增量增長(zhǎng),另外,它不會(huì)增長(zhǎng)到很大。
GC Heap的增量通常是6K。當(dāng)一個(gè)新的64K“內(nèi)存段”被創(chuàng)建時(shí),內(nèi)存分配將從該段中被分配,直到?jīng)]有足夠的空間,然后一個(gè)新的“內(nèi)存段”將被創(chuàng)建。Compact Framework GC使用標(biāo)準(zhǔn)Win32 API VirtualAlloc來(lái)分配新的內(nèi)存段。分配一個(gè)內(nèi)存段是很快的。在.NET Compact Framework 2.0的性能測(cè)試顯示,每秒可以分配750萬(wàn)個(gè)小型引用類型的實(shí)例。分配器之所以如此高效,是因?yàn)樗褂昧撕?jiǎn)單的算法。在GC堆中用一個(gè)指針指向下一個(gè)有效空間。當(dāng)分配一個(gè)新實(shí)例的空間時(shí),分配器簡(jiǎn)單地將指針移動(dòng)該對(duì)象所需要的字節(jié)數(shù)就可以了,如圖4所示:
Figure 4
分配一個(gè)新的引用類型只是簡(jiǎn)單地移動(dòng)“next object”指針
GC heap默認(rèn)的增量值是64K,在分配大于64K的對(duì)象時(shí),分配器將會(huì)以比較大的增量來(lái)增加Heap的尺寸。例如,如果一個(gè)程序嘗試創(chuàng)建一個(gè)70K的對(duì)象,分配器將創(chuàng)建一個(gè)新的70K的內(nèi)存段來(lái)存儲(chǔ)這個(gè)大對(duì)象。
Gc Heap會(huì)像我們描述得這樣一直增加,直到達(dá)到1MB。這時(shí)一個(gè)垃圾回收機(jī)制將被啟動(dòng)(更多地回收將在以后出現(xiàn))。在這以后,GC Heap也許會(huì)繼續(xù)增長(zhǎng),但是從上一次回收后,只要分配了1MB的對(duì)象,垃圾回收就會(huì)被啟動(dòng)。
Collecting Reference Types
現(xiàn)在我們已經(jīng)了解GC Heap是如何增長(zhǎng)的,讓我們來(lái)看看它是如何收縮的。此外,在內(nèi)存壓力下減少GC heap尺寸的能力是幫助應(yīng)用程序更好地運(yùn)行在內(nèi)存受限設(shè)備上的關(guān)鍵。在這一節(jié)中,我們將考慮那些原因會(huì)觸發(fā)一次垃圾回收和內(nèi)存何時(shí)被釋放并被返回給操作系統(tǒng)。
有一些原因會(huì)觸發(fā)垃圾回收機(jī)制。這些原因中的一個(gè)就是分配了1MB的托管對(duì)象。其他原因包括分配資源失敗,例如,無(wú)法分配更多的內(nèi)存,創(chuàng)建更多的Windows句柄等。請(qǐng)查看An Overview of the .Net Compact Framework Garbage Collector中對(duì)這些引發(fā)收回原因的詳細(xì)討論。
當(dāng)GC發(fā)生時(shí),內(nèi)存不是每次都會(huì)被釋放并返回給操作系統(tǒng)的。為了理解從操作系統(tǒng)角度來(lái)看內(nèi)存何時(shí)被釋放,讓我們來(lái)看一下當(dāng)垃圾回收運(yùn)行時(shí)到底發(fā)生了什么?
Unreferenced Objects
在每次垃圾回收的過(guò)程中,GC會(huì)檢索整個(gè)Heap以發(fā)現(xiàn)哪些對(duì)象不再被引用。這些不再使用對(duì)象的內(nèi)存將被釋放回GC Heap,使GC heap有更多可用空間。在釋放不再引用的類型后,一些64K的內(nèi)存段將有可能是完全空著的。如果內(nèi)存段是完全空的并且GC仍然有1MB以上的分配空間,空的64K內(nèi)存段將通過(guò)調(diào)用VirtualFree被歸還給操作系統(tǒng)。除了完整GC(原文為”full” GC,將在下面討論)外,收集器將會(huì)保持總數(shù)1MB的64K內(nèi)存段的緩存,即使其中的一些內(nèi)存段是空的。通過(guò)緩存代碼段的方式,我們通過(guò)減少調(diào)用VirtualAlloc和VirtualFree的次數(shù)來(lái)改善性能。
Compacting the GC Heap
在垃圾回收的過(guò)程中,GC將隨意地壓縮堆。當(dāng)一個(gè)堆充滿碎片時(shí),收集器將通過(guò)將所有活動(dòng)對(duì)象移動(dòng)到一起的方式,來(lái)“壓實(shí)”整個(gè)堆。壓實(shí)堆的主要目的是產(chǎn)生大量的有效內(nèi)存塊,以分配更多的對(duì)象。圖5表現(xiàn)了GC Heap中的內(nèi)容在壓縮前后的狀態(tài)。
Figure 5
The contents of a sample GC heap before and after compaction
通過(guò)”簡(jiǎn)單”對(duì)象回收,GC將歸還1MB內(nèi)存段緩存之外的空64K內(nèi)存段給操作系統(tǒng)。
A "Full" GC
在正常的事態(tài)發(fā)展下,GC將按照下面描述的方式來(lái)工作:在每次分配1MB之后,周期性的垃圾回收將被啟動(dòng);如果Heap充滿碎片,它將被壓縮;如果收集器有1MB的緩存,多余的空內(nèi)存段將被返還給操作系統(tǒng)。
事實(shí)上,在這三個(gè)設(shè)定之外,為了獲取更多的可用內(nèi)存,還會(huì)使用哪些更激烈地做法。這些做法和發(fā)生的時(shí)機(jī)密切相關(guān),就像第二章中JIT在這些時(shí)候減小它的Heap尺寸:當(dāng)分配內(nèi)存或其他資源失敗時(shí)發(fā)生,當(dāng)一個(gè)應(yīng)用程序被切換到后臺(tái)時(shí),或者當(dāng)應(yīng)用程序接收到操作系統(tǒng)的WM_HIBERNATE消息時(shí)。在這些時(shí)候,GC將不再保持在1MB的內(nèi)存段緩存上。(譯注:原文為the GC will hot hold onto its 1MB segment cache.嚴(yán)重懷疑是the GC will not hold onto its 1MB segment cache.否則意思不通,故改之)它將回收所有的不在引用的對(duì)象,壓縮堆,并且釋放所有可以釋放的內(nèi)存。
Pulling it All Together
現(xiàn)在我們已經(jīng)了解了GC如何分配和釋放內(nèi)存,讓我們看一下GC heap的尺寸在應(yīng)用程序的生命周期中的變化。
Figure 6
The size of the GC heap over the lifetime of an application.
圖6跟蹤了兩部分的數(shù)據(jù),黃色線表示在應(yīng)用程序的生命周期中,垃圾收集被請(qǐng)求分配的累計(jì)byte數(shù)。該數(shù)將持續(xù)增長(zhǎng),不會(huì)跳躍,就像應(yīng)用程序持續(xù)分配新對(duì)象。
圖6中的藍(lán)色線表示每個(gè)時(shí)間GC heap的尺寸。有幾個(gè)時(shí)間點(diǎn)值得注意。首先,當(dāng)應(yīng)用程序開(kāi)始并持續(xù)運(yùn)行時(shí),我們看到GC heap的尺寸的增長(zhǎng)和前面所見(jiàn)是一致的。圖中的每一步和新創(chuàng)建的64K GC段是一致的。第二,我們可以看到有時(shí)藍(lán)色線水平偏離。就像你在前邊關(guān)于分配和回收算法的討論中所了解的那樣,在1MB的時(shí)候heap尺寸變成水平。接下來(lái),你可以看到heap尺寸戲劇性地下降。下降的發(fā)生在我將應(yīng)用程序切換到后臺(tái)時(shí)。在應(yīng)用程序切換到前臺(tái)后,我們開(kāi)始看到heap再次以64K開(kāi)始增長(zhǎng)。
這個(gè)系列現(xiàn)在覆蓋了.Net Compact Framework如何管理內(nèi)存的基礎(chǔ),和構(gòu)建JIT編譯器和垃圾回收時(shí)的設(shè)計(jì)決定。在我的下一篇中,我們將討論在內(nèi)存受限設(shè)備中,CLR class loader如何運(yùn)行得更有效率。
This posting is provided "AS IS" with no warranties, and confers no rights.
Aawolf: Steven老兄最近好象對(duì)這個(gè)系列文章不感興趣了,沒(méi)有再更新。不過(guò)他的文章還是很有意思的,不多的文字就解釋了一些隱藏在背后的運(yùn)行機(jī)制。期待他的下一篇。
這是《設(shè)計(jì).Net Compact Framework CLR》的第三部分。在前面兩章中,我們討論了CLR如何管理內(nèi)存和JIT編譯器的基本設(shè)計(jì)原則。
Part I, Overview and Background
Part II, Jit Compiler Design Considerations
這一章我們主要討論垃圾收集設(shè)計(jì)中如何管理GC heap的問(wèn)題。
---------
討論.NET平臺(tái)如何管理內(nèi)存,垃圾收集肯定是第一個(gè)被提及的話題。不必驚訝,Compact Framework的垃圾收集和桌面版本有很多地方不同,原因是運(yùn)行環(huán)境的不同。就像Compact Framework CLR中的其他大部分子系統(tǒng)一樣,垃圾收集被設(shè)計(jì)成,在設(shè)備可用內(nèi)存較低時(shí),釋放所有能夠釋放的內(nèi)存。
對(duì)更高層次,GC提供了兩個(gè)基本功能:分配內(nèi)存保存引用類型的實(shí)例,在這些實(shí)例不再使用時(shí)收集他們。
Allocating Reference Types
通過(guò)第一章的討論,我們知道GC heap存在于進(jìn)程獨(dú)有的32M虛擬地址空間中。和JIT堆一樣,GC heap開(kāi)始時(shí)很小,隨著增長(zhǎng)需要更多的空間。然而,比較JIT堆,GC heap的增長(zhǎng)有兩個(gè)重要的不同:GC heap以一個(gè)固定的增量增長(zhǎng),另外,它不會(huì)增長(zhǎng)到很大。
GC Heap的增量通常是6K。當(dāng)一個(gè)新的64K“內(nèi)存段”被創(chuàng)建時(shí),內(nèi)存分配將從該段中被分配,直到?jīng)]有足夠的空間,然后一個(gè)新的“內(nèi)存段”將被創(chuàng)建。Compact Framework GC使用標(biāo)準(zhǔn)Win32 API VirtualAlloc來(lái)分配新的內(nèi)存段。分配一個(gè)內(nèi)存段是很快的。在.NET Compact Framework 2.0的性能測(cè)試顯示,每秒可以分配750萬(wàn)個(gè)小型引用類型的實(shí)例。分配器之所以如此高效,是因?yàn)樗褂昧撕?jiǎn)單的算法。在GC堆中用一個(gè)指針指向下一個(gè)有效空間。當(dāng)分配一個(gè)新實(shí)例的空間時(shí),分配器簡(jiǎn)單地將指針移動(dòng)該對(duì)象所需要的字節(jié)數(shù)就可以了,如圖4所示:
Figure 4
分配一個(gè)新的引用類型只是簡(jiǎn)單地移動(dòng)“next object”指針
GC heap默認(rèn)的增量值是64K,在分配大于64K的對(duì)象時(shí),分配器將會(huì)以比較大的增量來(lái)增加Heap的尺寸。例如,如果一個(gè)程序嘗試創(chuàng)建一個(gè)70K的對(duì)象,分配器將創(chuàng)建一個(gè)新的70K的內(nèi)存段來(lái)存儲(chǔ)這個(gè)大對(duì)象。
Gc Heap會(huì)像我們描述得這樣一直增加,直到達(dá)到1MB。這時(shí)一個(gè)垃圾回收機(jī)制將被啟動(dòng)(更多地回收將在以后出現(xiàn))。在這以后,GC Heap也許會(huì)繼續(xù)增長(zhǎng),但是從上一次回收后,只要分配了1MB的對(duì)象,垃圾回收就會(huì)被啟動(dòng)。
Collecting Reference Types
現(xiàn)在我們已經(jīng)了解GC Heap是如何增長(zhǎng)的,讓我們來(lái)看看它是如何收縮的。此外,在內(nèi)存壓力下減少GC heap尺寸的能力是幫助應(yīng)用程序更好地運(yùn)行在內(nèi)存受限設(shè)備上的關(guān)鍵。在這一節(jié)中,我們將考慮那些原因會(huì)觸發(fā)一次垃圾回收和內(nèi)存何時(shí)被釋放并被返回給操作系統(tǒng)。
有一些原因會(huì)觸發(fā)垃圾回收機(jī)制。這些原因中的一個(gè)就是分配了1MB的托管對(duì)象。其他原因包括分配資源失敗,例如,無(wú)法分配更多的內(nèi)存,創(chuàng)建更多的Windows句柄等。請(qǐng)查看An Overview of the .Net Compact Framework Garbage Collector中對(duì)這些引發(fā)收回原因的詳細(xì)討論。
當(dāng)GC發(fā)生時(shí),內(nèi)存不是每次都會(huì)被釋放并返回給操作系統(tǒng)的。為了理解從操作系統(tǒng)角度來(lái)看內(nèi)存何時(shí)被釋放,讓我們來(lái)看一下當(dāng)垃圾回收運(yùn)行時(shí)到底發(fā)生了什么?
Unreferenced Objects
在每次垃圾回收的過(guò)程中,GC會(huì)檢索整個(gè)Heap以發(fā)現(xiàn)哪些對(duì)象不再被引用。這些不再使用對(duì)象的內(nèi)存將被釋放回GC Heap,使GC heap有更多可用空間。在釋放不再引用的類型后,一些64K的內(nèi)存段將有可能是完全空著的。如果內(nèi)存段是完全空的并且GC仍然有1MB以上的分配空間,空的64K內(nèi)存段將通過(guò)調(diào)用VirtualFree被歸還給操作系統(tǒng)。除了完整GC(原文為”full” GC,將在下面討論)外,收集器將會(huì)保持總數(shù)1MB的64K內(nèi)存段的緩存,即使其中的一些內(nèi)存段是空的。通過(guò)緩存代碼段的方式,我們通過(guò)減少調(diào)用VirtualAlloc和VirtualFree的次數(shù)來(lái)改善性能。
Compacting the GC Heap
在垃圾回收的過(guò)程中,GC將隨意地壓縮堆。當(dāng)一個(gè)堆充滿碎片時(shí),收集器將通過(guò)將所有活動(dòng)對(duì)象移動(dòng)到一起的方式,來(lái)“壓實(shí)”整個(gè)堆。壓實(shí)堆的主要目的是產(chǎn)生大量的有效內(nèi)存塊,以分配更多的對(duì)象。圖5表現(xiàn)了GC Heap中的內(nèi)容在壓縮前后的狀態(tài)。
Figure 5
The contents of a sample GC heap before and after compaction
通過(guò)”簡(jiǎn)單”對(duì)象回收,GC將歸還1MB內(nèi)存段緩存之外的空64K內(nèi)存段給操作系統(tǒng)。
A "Full" GC
在正常的事態(tài)發(fā)展下,GC將按照下面描述的方式來(lái)工作:在每次分配1MB之后,周期性的垃圾回收將被啟動(dòng);如果Heap充滿碎片,它將被壓縮;如果收集器有1MB的緩存,多余的空內(nèi)存段將被返還給操作系統(tǒng)。
事實(shí)上,在這三個(gè)設(shè)定之外,為了獲取更多的可用內(nèi)存,還會(huì)使用哪些更激烈地做法。這些做法和發(fā)生的時(shí)機(jī)密切相關(guān),就像第二章中JIT在這些時(shí)候減小它的Heap尺寸:當(dāng)分配內(nèi)存或其他資源失敗時(shí)發(fā)生,當(dāng)一個(gè)應(yīng)用程序被切換到后臺(tái)時(shí),或者當(dāng)應(yīng)用程序接收到操作系統(tǒng)的WM_HIBERNATE消息時(shí)。在這些時(shí)候,GC將不再保持在1MB的內(nèi)存段緩存上。(譯注:原文為the GC will hot hold onto its 1MB segment cache.嚴(yán)重懷疑是the GC will not hold onto its 1MB segment cache.否則意思不通,故改之)它將回收所有的不在引用的對(duì)象,壓縮堆,并且釋放所有可以釋放的內(nèi)存。
Pulling it All Together
現(xiàn)在我們已經(jīng)了解了GC如何分配和釋放內(nèi)存,讓我們看一下GC heap的尺寸在應(yīng)用程序的生命周期中的變化。
Figure 6
The size of the GC heap over the lifetime of an application.
圖6跟蹤了兩部分的數(shù)據(jù),黃色線表示在應(yīng)用程序的生命周期中,垃圾收集被請(qǐng)求分配的累計(jì)byte數(shù)。該數(shù)將持續(xù)增長(zhǎng),不會(huì)跳躍,就像應(yīng)用程序持續(xù)分配新對(duì)象。
圖6中的藍(lán)色線表示每個(gè)時(shí)間GC heap的尺寸。有幾個(gè)時(shí)間點(diǎn)值得注意。首先,當(dāng)應(yīng)用程序開(kāi)始并持續(xù)運(yùn)行時(shí),我們看到GC heap的尺寸的增長(zhǎng)和前面所見(jiàn)是一致的。圖中的每一步和新創(chuàng)建的64K GC段是一致的。第二,我們可以看到有時(shí)藍(lán)色線水平偏離。就像你在前邊關(guān)于分配和回收算法的討論中所了解的那樣,在1MB的時(shí)候heap尺寸變成水平。接下來(lái),你可以看到heap尺寸戲劇性地下降。下降的發(fā)生在我將應(yīng)用程序切換到后臺(tái)時(shí)。在應(yīng)用程序切換到前臺(tái)后,我們開(kāi)始看到heap再次以64K開(kāi)始增長(zhǎng)。
這個(gè)系列現(xiàn)在覆蓋了.Net Compact Framework如何管理內(nèi)存的基礎(chǔ),和構(gòu)建JIT編譯器和垃圾回收時(shí)的設(shè)計(jì)決定。在我的下一篇中,我們將討論在內(nèi)存受限設(shè)備中,CLR class loader如何運(yùn)行得更有效率。
This posting is provided "AS IS" with no warranties, and confers no rights.
Aawolf: Steven老兄最近好象對(duì)這個(gè)系列文章不感興趣了,沒(méi)有再更新。不過(guò)他的文章還是很有意思的,不多的文字就解釋了一些隱藏在背后的運(yùn)行機(jī)制。期待他的下一篇。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
