.NET的垃圾回收機制是一個很是強大的功能,儘管咱們不多主動使用,但它一直在默默的在後臺運行,咱們仍須要意識到它的存在,瞭解它,作出更高效的.NET應用程序;下面我分享一下我對於垃圾回收機制(GC)的學習心得。html
咱們知道程序會須要向內存堆使用new請求內存,而後將請求的內存初始化並使用,使用完畢以後,變清理資源和釋放內存,等待別的程序來請求使用;對內存資源的管理方式,如今存在這麼幾種管理方式:算法
一、手動管理:C、C++windows
二、計數管理:COMapp
三、自動管理:.NET、JAVA、PHPide
如今的高級語言基本上都實現了自動管理內存,這是由於手動管理內存會由於人爲的緣由產生如下問題:函數
一、開發人員忘記釋放請求的內存,形成內存泄漏,如果內存泄露過多,則可能會形成內存溢出,致使程序沒法運行;post
二、應用程序訪問已釋放的內存,形成數據讀取錯誤。性能
因而可知,手動去管理堆裏面的內存可靠程度,會因開發人員的不一樣而不一樣,在C++因指針而出現的問題可很多;並且易出現Bug等亂七八糟的問題,影響系統穩定性,因此自動化管理內存是必要的。學習
當應用程序分配新的對象,GC的代的預算大小已經達到閾值,好比GC的第0代已滿;優化
代碼主動顯式調用System.GC.Collect();
其餘特殊狀況,好比,windows報告內存不足、CLR卸載AppDomain、CLR關閉,甚至某些極端狀況下系統參數設置改變也可能致使GC回收。
應用程序根(application root):根(root)就是一個存儲位置其中保存着對託管堆上一個對象的引用,根能夠屬性下面任何一個類別
垃圾回收器將託管堆(heap)裏面的對象劃分爲3個代(通常爲3代),可使用GC.MaxGeneration()方法來進行查詢當前系統所支持的最大代數:
一、G0 小對象(Size<85000Byte):新分配的小於85000字節的對象
二、G1:在GC中倖存下來的G0對象
三、G2:大對象(Size>=85000Byte);在GC中倖存下來的G1對象
當一個對象被new的時候,它的代爲0,通過一次回收以後,若該對象沒有被回收,則代上升,變爲1,若每次回收都倖存下來,則代都會上升,最大代爲操做系統所支持的最大代。
由於將對象以代劃分,而且能夠單獨回收某一個世代,避免回收整個託管堆,提高性能。一個基於代的垃圾回收器有一下特色:
一、對象越新,生存期越短;
二、對象越老,生存期越長;
三、回收堆的一部分,速度快於回收整個堆。
在垃圾回收的第一步就是標記對象:垃圾回收器會認爲託管堆中的全部對象都是垃圾,而後垃圾回收器會去檢查全部的應用程序根,遍歷每一個根所引用到的對象,將其標記爲活動的(live ),全部的根對象都檢查完以後,有標記的對象就是可達對象,未標記的對象就是不可達對象,不可達對象就是回收的目標。
弱引用對象則不在考慮範圍以內,因此必定會被回收掉的。
在通過第一步的對象篩選以後,回收沒有被引用的對象,就是不可達對象,GC調用對象默認的終結器Finalize(),銷燬對象以後,將內存也釋放掉。
同時,還存在引用的對象,就是可達對象的世代變爲下一個世代。
通過第二步的銷燬對象和釋放內存以後,倖存下來的對象在堆中的排列多是不連續的,這時在堆中存在很是多的內存碎片,程序在new對象的時候都是請求一段連續的內存,則內存碎片可能就沒法再次利用(雖然沒有被使用),形成內存資源的浪費,因此垃圾回收的最後一步就是壓縮內存:將垃圾回收後倖存的對象移動到一塊兒,而且將各個對象的引用更新到對象新的位置上,保證對象引用的正確性。
注:從這裏看得出,在壓縮堆內存的時候,全部相關線程必須暫停,由於壓縮時不能保證對象引用的正確性,因此在垃圾回收的時候,GC會劫持全部相關線程,在回收完畢以後,被劫持的線程纔會正常工做,因此垃圾回收勢必會影響必定的性能,因此慎用System.GC.Collect()。
上面說到,GC在回收對象的時候是調用對象的終結器Finalize()來實現的,那麼,就簡單的總結一下Finalize()與Dispose()吧:
一、調用者:
Finalize只能由GC調用
Dispose由開發人員顯示調用,也可使用use區塊,在程序離開區塊使自動調用Dispose方法
二、調用時機:
Finalize因爲是GC調用的,因此調用時機是垃圾回收的時候調用,時機不肯定
Dispose因爲是顯示調用,因此調用時機是肯定的,在調用方法的時候就調用了
三、目的:
這裏的目的主要說是Dispose出現的目的;
首先是.NET存在託管資源和非託管資源,通常來講,非託管資源數量有限,比較珍貴,在使用完畢以後,但願可以釋放掉,那麼將釋放非託管資源的方法寫到終結器Finalize裏面也是能夠的,可是因爲Finalize的調用時機不肯定,致使釋放資源不及時,那麼有限的非託管資源很快就被佔用完畢,因此,爲了可以及時的釋放掉這類資源,咱們須要可以顯示調用的方法,這就是Dispose。
Finalize主要是爲了GC釋放託管資源和銷燬對象,釋放內存
Dispose主要是爲了釋放託管和非託管資源和銷燬對象,釋放內存
注:沒必要擔憂資源的重複釋放問題,就算是重複釋放,.NET也作好了相應措施來處理,不會拋出異常。
下面貼一個MSDN推薦的標準的Dispose實現方式
class Class : IDisposable { // 標識:是否釋放託管資源 private bool disposed = false; // 顯示調用的方法 public void Dispose() { Dispose(true); // 將對象從垃圾回收器鏈表中移除, // 從而在垃圾回收器工做時,只釋放託管資源,而不執行此對象的析構函數 GC.SuppressFinalize(this); } // 受保護的釋放資源方法 protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { // 此處寫釋放託管資源的方法 } disposed = true; // 此處寫釋放非託管資源的方法 } } ~Class() { // 這裏是防止忘記顯示調用Dispose(),在GC進行垃圾回收的時候進行釋放非託管資源 Dispose(false); } }
GC所帶來的便利是不言而喻的,可是這是付出必定的系統性能來實現的:在垃圾回收的時候GC會劫持全部相關的線程,而且會有必定的時空開銷,因此在平時開發過程當中注意一些良好的開發習慣可能會對GC有一些積極的影響。
一、儘可能不要new很大的對象,大對象(>=85000Byte)直接歸爲G2代,GC回收算法歷來不對大對象堆(LOH)進行內存壓縮整理,移動大對象將會消耗更多的CPU時間,也更容易形成內存碎片。這裏也能夠將大對象或者生命週期長的對象進行池化。
二、不要頻繁的new生命週期短的小對象,這可能會致使頻繁的垃圾回收,這裏能夠考慮使用結構體放在棧中來代替,或者也可使用對象池化來優化。
三、不推薦使用對象池化的解決方案,它比較笨重和容易出錯,設計一個高性能穩定的對象池並不容易。
四、下降對象之間的縱向深度,GC在回收過程當中,會先順着根來進行對象遍歷和標記,減小深度能夠加快遍歷速度;若系統中各個類之間的關係錯綜複雜,那麼考慮一下設計方案是否合理。
固然注意的地方還有很多,最後貼一篇博客,這裏介紹了如何編寫高性能的.NET代碼,其中的GC介紹很是詳細: