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

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