章節安排程序員
內存管理簡介算法
對於任何一種編程語言,內存管理都是不得不提很重要的一塊內容,但惋惜的是目前爲止沒有任何一種編程語言對內存管理處理的很是完美,每種語言都在兼顧性能 效率,語法語義易用性等方面折中中有所側重。例如較之於C#,JAVA等語言C++號稱不須要垃圾收集,由於C++自己產生的垃圾不多,誠然這是C++的 優點,這也就是爲何在內存受限或者效率優先的環境下優先考慮C++,但它的缺點也是明顯的--程序員必須本身控制內存管理,很容易產生內存泄漏,這同時 也造就了C++很難掌握。感謝摩爾定律吧,它促使了垃圾收集這個概念的出現,但較之C++直接操縱內存釋放,再牛逼的垃圾收集算法也沒法抹去那一層性能上 的損失。數據庫
在討論以前咱們先明確一點:內存中數據按所處位置不一樣能夠分爲棧內存和堆內存,棧的主要做用是追蹤函數調用之間的數據傳遞(棧上所存儲 的數據類型一般是int,char,long,指針等內置值類型和struct。注意一點在多線程環境下,每一個線程都有本身的棧。)因此棧的內存管理一般 由操做系統負責。而咱們所說的內存管理,大都討論的是堆上內存管理(分配在堆上的類型通常是自定義引用類型:類,接口,字符串,對象實例,C#中委託 等)。關於這一點詳情請參照Under the hood of NET Management。編程
內存內存管理從生命週期上來分能夠分爲三個階段:內存分配,內存生命週期內管理,內存的釋放。每一階段都與程序的運行效率關係密切,以C++爲例,在新版 的C++標準中Unique_ptr取代auto_ptr,move語義,引入右值引用等措施極大地提升了STL的效率(詳細信息參考Refereces 中關於C++的連接)。而兼顧討論內存管理的全部內容有點不現實,本篇主要關注內存的釋放,確切來說是C#的垃圾回收。 性能優化
垃圾回收機制 數據結構
首先聲明一點所謂垃圾回收,回收的是分配在託管堆上的內存,對於託管堆外的內存,它無能爲力。多線程
討論垃圾回收機制就不得不提內存的分配,在C運行時堆(C-runtime heap)中,堆是不連續的,咱們new一個新的對象時,系統會檢查內存,找一塊足夠大的內存而後初始化對象,對象被銷燬後,這塊空間會用於初始化新的對 象。這樣作有什麼弊端?隨着程序運行一直有對象生成釋放,內存會變得碎片化,這樣有新的大的對象要生成時就必須擴展堆的長度,碎片內存沒法獲得充分利用, 還有一個弊端是每次建立一個對象時都要檢查堆內存,效率不高。而C#託管堆採起連續內存存儲,新建立對象時只要考慮剩下的堆內存是否足夠大就成,但一直生 成對象而不析構會使託管堆無限增大,怎麼維護這樣一塊連續內存呢?這也就引出了垃圾回收機制。託管堆的大小是特定的,垃圾收集器GC負責當內存不夠的時候 釋放掉垃圾對象,copy仍在使用的對象成一塊連續內存。而這就帶來了性能問題,當對象很大的時候,頻繁的copy移動對象會下降性能,因此C#的垃圾收 集引入了世代和大對象堆小對象堆的概念。 編程語言
所謂大對象堆小對象堆從字面意義就能看出其做用,大對象堆主要負責分配大的對象,小對象堆分配小的。但對象大小怎麼肯定呢?在.NET Framework中規定,若是對象大於或等於 85,000 字節,將被視爲大型對象。當對象分配請求傳入後,若是符合該大小閾值,便會將此對象分配給大型對象堆。這個85000字節是根據性能優化的結果肯定。值得 注意的是垃圾回收對大對象堆和小對象堆都起做用。那麼分大對象和小對象堆做用是什麼呢?仍是性能,對於大對象和小對象區別對待採起不一樣靈活的垃圾回收策略 一定比一棍子打死死板的採用同一種策略要好。下面咱們討論一下在SOH和LOH不一樣的垃圾收集策略: 函數
先說一下世代,之因此分世代,是由於在第0代就能清除大部分對象。請注意,世代是個邏輯上的概念,物理上並無世代這個數據結構。以小對象堆垃圾回收爲 例:當一個對象被建立的時候,它被定義爲第0代對象,而經歷一次垃圾收集後還存餘的對象就被納入了第1代對象,同理通過兩次或者兩次以上仍然存在的對象就 能夠當作第2代對象。雖然世代只是邏輯概念,但它倒是有大小的,對於SOH對象來講,因爲每次垃圾回收都會壓縮移動對象,因此世代數越大越在堆底。經歷一 次垃圾回收,對象都會被移入下一個世代的內存空間中(下圖以小對象堆上垃圾回收爲例。對象W至少通過兩次垃圾回收而不死,因此放入世代2,X經歷了一次垃 圾回收)。而每次一個世代內存達到其闕值,都會引起垃圾收集器回收一次。這麼作的好處就是每次垃圾回收器只是回收一個世代的內存,從總體上來看,減小了對 象複製移動的次數。 工具
以上討論都是針對於SOH,對於SOH對象來講複製移動較之LOH成本性能要小一點,那麼對於LOH呢?複製一個LOH的對象而且清除原來內存位置的字 節,成本至關大,怎麼確保它的垃圾回收呢?首先從物理上來講,LOH在託管堆的堆底,SOH在其上,邏輯上講LOH對象都分配在第二世代,也就是說前邊第 0代和第1代的垃圾收集回收的都是第OH對象,這也就解釋了爲何前兩個世代垃圾回收中容許複製移動對象。但對於第二世代垃圾回收呢?第二代垃圾回收以後 的對象還是第二世代,其回收時並不移動仍在使用的對象,壓縮空間,而只是清除垃圾對象。當一個新LOH對象建立時,它會從堆底遍歷尋找LOH中可以知足要 求的內存,若是沒有接着向堆頂建立(這個過程和C運行時工做原理同樣,因此也存在相同的弊端,LOH堆內存有可能存在碎片)。此時若是堆頂已經超出闕值, 引起垃圾回收器回收內存空間。
從上邊討論中咱們能夠總結一下:咱們new出一對象時它要麼小對象會被放入第0代,大對象會被分在LOH中,只有垃圾回收器纔可以在第1代和第2代中「分配」對象,這裏所說分配對象是指移動複製對象。
以上就是垃圾回收機制,有一塊最重要的一點沒有討論,就是垃圾收集器GC怎麼判斷該對象是垃圾對象。關於這一點能夠參考連接。
性能問題
由上邊討論咱們能夠看出,自動化垃圾回收是須要付出成本的,而世代和大對象堆/小對象堆這些概念的引入是儘量的下降這一成本。但性能問題不可避免,性能 數據分析能夠很好的幫助咱們瞭解避免些許問題,關於性能分析工具及方法請參考References中連接。
C#下非託管資源的處理--Disposable模式
咱們上邊說過,垃圾回收器只能收集託管堆的內存,但對於堆外內存好比HWnds,數據庫鏈接,GDI句柄,safeHandle。這樣就有一個問題:垃圾 收集器不會肯定地運行,其結果可能會使您的對象在上次引用以後很長時間不能被終結。若是你的對象佔用了昂貴或稀少的資源(如一個數據庫鏈接),這是不能被 接受的。爲了不無休止地等待垃圾收集器運行,擁有資源的類型應該實現 IDisposable 接口,而後該類型資源的使用方會及時地釋放那些資源。Joe Duff在其網站中給了相關注意細節,並提供了一種Disposable模式。能夠參考一下連接瞭解一下,使你的程序寫的更加優雅健壯。(MSDN關於 IDisposable例子中也採用這種方法)
http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae
要強調的幾點
1. 值得注意的是雖然微軟目前不會移動壓縮LOH,可是未來可能會,因此若是分配了大型對象並但願確保它們位置不被移動,則應該將其固定起來。
2. 這篇文章是基於本人理解,有可能有出入,詳細信息能夠參照連接,並歡迎指正。
References
C++
http://zh.wikipedia.org/wiki/C++0x
http://blog.csdn.net/zentropy/article/details/6973411
http://www.codeproject.com/Articles/71540/Explicating-the-new-C-standard-C-0x-and-its-implem#RValues http://www.codeproject.com/Articles/101886/Standard-C-Library-Changes-in-Visual-C-2010
C#
http://msdn.microsoft.com/zh-cn/magazine/bb985011(en-us).aspx
http://msdn.microsoft.com/zh-cn/magazine/cc534993.aspx
http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae
性能問題和多語言交互
http://msdn.microsoft.com/zh-cn/magazine/ee309515.aspx
http://msdn.microsoft.com/zh-cn/magazine/cc163528.aspx
http://msdn.microsoft.com/zh-cn/magazine/cc163316.aspx
http://msdn.microsoft.com/zh-cn/magazine/cc163392.aspx
垃圾收集發展史:
http://blog.csdn.net/KAI3000/article/details/314628