淺談你感興趣的 CLR GC 機制底層


本文內容是學習CLR.via C#的21章后個人整理,有不足之處歡迎指導。

昨天是1024,coder的節日,我為自己coder之路定下一句准則--保持學習,保持自信,保持謙遜,保持分享,越走越遠。

第一部分—基本原理思想

   垃圾回收機制是針對托管堆而言。

   不同於C的運行時堆,托管堆是內存是連續的,每次分配新內存,NextObjPtr指針只需要加上新分配內存塊大小即可。C運行時堆為了維護鏈表的完整性,每當分配新的內存時,遍歷鏈表,一旦發現足夠大的內存塊,則拆分塊,修改節點中的指針。從托管堆中分配內存的速度,幾乎可以與線程棧分配相媲美。

   GC機制回收的就是托管堆中的垃圾對象。

 

第二部分—基本算法思想

   GC檢查托管堆中是否有不再使用的對象。

   那么什么是不再使用的對象?

   首先要解釋什么是根(root)。每個應用程序都有一組根,每個根都是一個存儲位置,其中包含指向引用類型對象的一個指針。該指針要么運用托管堆中的一個對象,要么為null。類型中定義的任何靜態字段被認為是一個根,任何方法參數或者局部變量也被認為是一個根。只有引用類型變量,才被認為是根,值類型的變量永遠不被認為是根。

   GC開始執行時,假設所有對象都是垃圾。

   GC沿着線程棧上檢查所有的根,如果發現了一個跟引用堆中的對象,則在這個對象的“同步索引字段”上開啟一位,也就是將這個bit設置為1,也就是說這個對象被標記了。

   GC就是這樣,以遞歸的方式遍歷所有可達的對象。可達的對象也就是說有根的對象,就是在標記階段被標記的,也就是本次不回收的。所以不可達的對象就被回收了。

   進入第二階段--壓縮階段。

   實際上此壓縮非彼壓縮,在這里是指碎片整理,如何整理呢?如果發現曉得內存塊,GC忽略它們,如果發現大的,可用的連續內存塊,GC把非垃圾對象移動到這里以壓縮堆。

   包含只想這些對象的指針的變量和CPU寄存器,現在都會變得無效,NextObjPtr也應重新指向托管堆的結尾。

  

第三部分—列表和F-reachable隊列

   說到終結列表要從某些不僅占用內存的對象說起,比如FileStream,它不僅占用內存資源,也在占用本地資源。

   Finalize方法就是用於釋放本地資源的方法。

   那么這個終結列表用來做什么呢?微軟當然不會畫蛇添足,請仔細看好下面這段:

   既占用內存資源,又占用本地資源的,在GC回收這樣對象所占的內存時,僅僅回收內存時不夠的,因為一定要調用Finalize方法來釋放本地資源啊!強烈不建議在代碼中我們手動Finalize,這需要堆Finalize的實現有相當深刻並且全面的理解。那么微軟的GC是什么怎么來給我們執行的呢?這就用到了終結列表,為了一定要保證執行Finalize,在最初我們new操作符分配內存地時候,如果該對象的類型中定義了Finalize方法,那么將該對象的一個指針方法到終結列表當中,當此類對象在托管堆中判定為垃圾的時候,GC掃描終結列表,以查找這些對象的指針,該指針會從終結列表中移除,並追加到F-reachable隊列。

   那么這個隊列干嘛的呢?我認為唯一的目的就是復活對象,並調用Finalize方法釋放本地資源(如果對象是死的,我們無法調用其方法),調用方法的是一個微軟定義好的優先級比較高的一個線程,聽說這樣做有很多好處。那么為什么放到F-reachable隊列中就復活了?f(finalization)終結,reachable可達的。換言之,可將這個隊列看做靜態字段那樣的一個跟。

  

第四部分—代的思想和原理

   GC機制無論何時,都分為三代,0代,1代,2代。

   代是什么,微軟關於這個做了假設,新對象生存周期比較短,而老對象傾向於活的久一些。所在代越高的對象,存活的越久,所在代越低的對象,越容易被回收。

   代就是托管堆中被分配的內存而已,也可以說把托管堆分成三部分吧?

   0代初始的預算大小為256kb,當0代中的內存用完時,為新對象分配內存,0代內存不夠用時,GC開始回收第一代,未被標記的對象當然回收,已標記的對象則這些對象提高一代,進入1代區域,與此同理,當一代內存已滿時,回收1代,此時根對象也就是標記的對象提升至2代。

   再次重點說一下三代預算大小分別為256KB,2MB,10MB,預算大小以提升性能為宜,預算越大,垃圾回收頻率越低。再次注意的是,性能提升的理論源於開始的假設:新對象生存期較短,老對象傾向於活的久一些。請仔細看下面一段。

   如果GC回收后,0代存活下來的對象很少,或者說回收的內存很多。0代預算可能會從256調整為128。代的分配空間減少,意味着回收頻繁回收頻繁,但GC所做的工作會減少,從而減小進程的工作集,最理想的狀態是0代對象都是當做垃圾被回收,這樣不必壓縮內存,NextObjPtr指向0代起始處。這樣來講,最開始的假設是是成立的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM