若是不能徹底避免大對象堆的分配,則要儘可能避免碎片化。
對於LOH不當心就會有無限增加,但LOH使用的空閒列表機制能夠減輕增加的影響。利用這個空閒列表,咱們能夠在兩塊分配區域中間找到你所想要的可分配區域。
要作到這一點,就須要保證你在LOH裏的分配都按照同一個尺寸或者同一個尺寸的倍數進行。例如,一個常見的需求是在LOH裏分配緩衝區。要確保分配的每一個緩衝區都是一個大小,或者是一個知名數字(1M)的倍數,而不要建立大小不一的緩衝區。這樣作的話,若是一個緩衝區被回收,那麼下一個緩衝區在分配的時候,很大機率不會在堆結尾分配,而是會在被回收的地方從新分配。算法
繼續用前面的MemoryStreams的的故事。咱們的第一個實現咱們只對PooledMemoryStream進行的池化,它的緩衝區增加仍是沿用MemoryStreams的默認算法,當超過容量是,會按照當前的緩衝區大小加倍申請。這雖然解決分配問題,可是又形成了碎片問題。第二次迭代的時候,咱們拋棄了這種申請算法,咱們傾向於實現一個流的抽象類,將多個128K直接的緩衝區合併使用,將這些小的緩衝區用連接的方式組成一個大的緩衝區,他們大小爲1MB的倍數(最大爲8MB)。這個新的實現大大減小了咱們的碎片問題,固然咱們偶爾還會不得不將一些128KB的數據複製到1MB的緩衝區裏,但這樣的改進也是值得的。緩存
在幾乎全部的正常狀況下,你是不該該主動執行完整GC操做的,這可能會打亂GC的自動處理流程,致使一些很差的結果。可是,在一些高性能系統裏存在一些狀況,咱們仍是會建議你進行一次完整GC。
一般,在有合適的時間窗口下進行完整GC,能夠避免在從此很差的時間段執行GC。注意,這裏討論的只是耗時比較多完整GC,對於0代和1代的回收仍是應該頻繁出發,以免構建的0代內存區太大。服務器
在下面狀況能夠作一次完整的完整GC:app
你若是使用了低延遲模式,在這種模式下,堆的大小會一直增加,這個時候你須要在合適的時間點來執行一次完成GC。oop
若是會偶爾大量分配一些長生命週期的對象(初始化對象池),在對象建立後,能夠執行一次完整GC,將對象儘快轉爲2代對象。或者當你再也不使用這些對象,也最好在刪除引用後強制回收他們。性能
若是你如今所處的狀態,由於碎片太多,必需要作大對象堆作壓縮的時候。測試
對於狀況1,2都是在特定時間裏經過強制執行GC來避免在不合適的時機被執行GC。狀況3,若是你在LOH裏有很大的碎片,則能夠幫助你減小堆的大小。若是不是上面的狀況,你最好另外想一些其它優化方案。優化
要執行完整GC,可使用GC.Collect來回收所但願的代紀。還能夠經過GCCollectionMode的枚舉參數告訴GC是否當即執行。參數有3個值
Default--(默認)當前,強制
Forced--(強制)告訴GC當即開始收集
Optimized--(優化)由GC決定如今是不是要的時機執行回收this
GC.Collect(2); // 等價於 GC.Collect(2, GCCollectionMode.Forced);
即便使用了對象池,仍然可能會在大對象堆裏分配對象,隨着時間的推移,在裏面會存在不少碎片。從.NET 4.5.1 開始,你能夠告訴GC在下一次作完整GC時順便也對LOH作一次壓縮。線程
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
根據LOH的大小,這個壓縮過程可能會很慢,甚至會用到好幾秒。你最好是在你的程序可以長時間暫停的時候,才讓垃圾回收器作一次這樣的完整GC。修改該設置值,只會在下一次完整GC時會觸發壓縮,一旦完成了LOH的壓縮,GCSettings.LargeObjectHeapCompactionMode就會被從新設置爲GCLargeObjectHeapCompactionMode.Default。
由於這個過程很耗時,我仍是建議你減小對LOH的分配或者使用對象池。這樣將大大減小壓縮的數據。壓縮LOH功能只能做爲碎片過多,分配的堆太大時的最後手段。
若是你的應用徹底不但願受到2代的的GC影響,你能夠在GC快來臨前收到一個通知。這樣能夠給你一個機會,暫停現有的業務處理,將請求分流到其它服務器,或者進入某種對你更合理的狀態。
可是我建議你謹慎使用,這個GC通知機制可能會給你產生一些意料以外的狀況。你應該在全部的優化手段都使用後才考慮它。若是你有下面的狀況,你能夠利用GC通知功能。
2代的回收起始不多發生,更多的時候是在不少0代小對象分配時會達到觸發的閾值,因此在收到GC的通知時,你還有不少工做須要作。
不幸的是,因爲GC通知觸發的不精確性,你只能在1-99範圍你指定一個合適的觸發時機。若是數字比較小,你可能會在裏真正GC前纔會收到消息,沒有足夠的時間作相應處理。但若是你的數字過高,這可能會被頻繁觸發而不會觸及真正的GC。這兩個選擇取決你當前內存的分配率與內存負債。注意,這裏會指定2個閾值數字,一個用於2代對象,一個用於LOH。與其它功能同樣,GC會盡最大努力給你通知,但它不會保證你能不作此次GC。
要使用此功能,請按照一下步驟進行。
由於通知須要一個輪詢的機制,你須要有一個線程按期的檢查狀態。若是你的程序裏已經有這樣的定時檢查功能,你能夠將它嵌入到檢查流程裏。固然也能夠單獨爲GC檢查建立一個獨立的線程。
下面的是一個 GCNotification 的完整例子。它會不斷的分配內存用來測試通知過程。
internal class Program { private static void Main(string[] args) { const int ArrSize = 1024; var arrays = new List<byte[]>(); GC.RegisterForFullGCNotification(25, 25); // Start a separate thread to wait for GC notifications Task.Run(() => WaitForGCThread(null)); Console.WriteLine("Press any key to exit"); while (!Console.KeyAvailable) { try { arrays.Add(new byte[ArrSize]); } catch (OutOfMemoryException) { Console.WriteLine("OutOfMemoryException!"); arrays.Clear(); } } GC.CancelFullGCNotification(); } private static void WaitForGCThread(object arg) { const int MaxWaitMs = 10000; while (true) { // There is also an overload of WaitForFullGCApproach // that waits indefinitely GCNotificationStatus status = GC.WaitForFullGCApproach(MaxWaitMs); bool didCollect = false; switch (status) { case GCNotificationStatus.Succeeded: Console.WriteLine("GC approaching!"); Console.WriteLine("-- redirect processing to another machine -- "); didCollect = true; GC.Collect(); break; case GCNotificationStatus.Canceled: Console.WriteLine("GC Notification was canceled"); break; case GCNotificationStatus.Timeout: Console.WriteLine("GC notification timed out"); break; } if (didCollect) { do { status = GC.WaitForFullGCComplete(MaxWaitMs); switch (status) { case GCNotificationStatus.Succeeded: Console.WriteLine("GC completed"); Console.WriteLine("-- accept processing on this machine again --"); break; case GCNotificationStatus.Canceled: Console.WriteLine("GC Notification was canceled"); break; case GCNotificationStatus.Timeout: Console.WriteLine("GC completion notification timed out"); break; } // Looping isn't necessary, but it's useful if you want // to check other state before waiting again. } while (status == GCNotificationStatus.Timeout); } } } }
另一種觸發方式是壓縮LOH堆,可是基於內存使用觸發更合適一些。
被弱引用對象引用的對象時能夠在GC的時候被回收的。這與強引用造成對別,強引用後的對象是不會被回收的。弱引用主要用來緩存你想保留的不是很重要的對象,一旦應用有內存上的壓力,就有可能被回收。
WeakReference weakRef = new WeakReference(myExpensiveObject); … // Create a strong reference to the object, // now no longer eligible for GC var myObject = weakRef.Target; if (myObject != null) { myObject.DoSomethingAwesome(); }