標籤:blog http os 使用 java ar strong 資料 div
章節安排
- 記憶體管理簡介
- 記憶體回收機制
- 效能問題
- C#下非託管資源的處理
- 要強調的幾點
- References
記憶體管理簡介
對於任何一種程式設計語言,記憶體管理都是不得不提很重要的一塊內容,但可惜的是目前為止沒有任何一種程式設計語言對記憶體管理處理的非常完美,每種語言都在兼顧效能 效率,文法語義易用性等方面折中中有所側重。例如較之於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://en.cppreference.com/w/
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
http://blog.csdn.net/hellothere/article/details/2115422
http://blog.csdn.net/hellothere/article/details/2245734
C#下記憶體管理--垃圾收集