先來看MSDN的解釋:初始化新進程時,運行時會爲進程保留一個連續的地址空間區域。這個保留的地址空間被稱爲託管堆。算法
「託管堆也是堆」,爲何這樣說呢?這麼說是但願你們不要被「術語」迷惑,這個知識點的前提是「值類型和引用類型的區別」。這裏假設讀者已經知道「值類型存儲在棧中,引用類型存儲在堆中。(引用類型的引用存儲在棧中)」這一重要概念。因此,根據這個理論,除值類型外,CLR要求全部資源都從託管堆分配。編程
託管堆維護着一個指針,這裏命名爲NextObjPtr,它指向下一個對象在堆中的分配位置。緩存
這個是計算機基礎知識,這裏複習一下,有助於對下面「根」概念的理解。編程語言
CPU寄存器是CPU本身的」臨時存儲器」,比內存的存取還快。按與CPU遠近來分,離得最近的是寄存器,而後緩存(計算機1、2、三級緩存),最後內存。性能
類中定義的任何靜態字段,方法的參數,局部變量(僅限引用類型變量)等都是根,另外cpu寄存器中的對象指針也是根。根是CLR在堆以外能夠找到的各類入口點。優化
若是一個根引用了堆中的一個對象,則該對象爲「可達」,不然便是「不可達」。3d
從計算機組成的角度來說,全部的程序都是要駐留在內存中運行的。而內存是一個限制因素(大小)。除此以外,託管堆也有大小限制。若是託管堆沒有大小限制,那C#的執行速度要優於c了(託管堆的結構讓它有比c運行時堆更快的對象分配速度)。由於地址空間和存儲的限制因素,託管堆要經過垃圾回收機制,來維持它的正常運做,保證對象的分配,不會「內存溢出」。指針
回收分爲兩個階段: 標記 –> 壓縮對象
標記的過程,其實就是判斷對象是否可達的過程。當全部的根都檢查完畢後,堆中將包含可達(已標記)與不可達(未標記)對象。blog
標記完成後,進入壓縮階段。在這個階段中,垃圾回收器線性的遍歷堆,以尋找不可達對象的連續內存塊。並把可達對象移動到這裏以壓縮堆。這個過程有點相似於磁盤空間的碎片整理。
如上圖所示,綠色框表示可達對象,黃色框爲不可達對象。不可達對象清除後,移動可達對象實現內存壓縮(變得更緊湊)。
壓縮以後,「指向這些對象的指針」的變量和CPU寄存器如今都會失效,垃圾回收器必須從新訪問全部根,並修改它們來指向對象的新內存位置。這會形成顯著的性能損失。這個損失也是託管堆的主要缺點。
基於以上特色,垃圾回收引起的回收算法也是一項研究課題。由於若是真等到託管堆滿纔開始執行垃圾回收,那就真的太「慢」了。
代是CLR垃圾回收器採用的一種機制,它惟一的目的就是提高應用程序的性能。分代回收,速度顯然快於回收整個堆。
CLR託管堆支持3代:第0代,第1代,第2代。第0代的空間約爲256KB,第1代約爲2M,第2代約爲10M。新構造的對象會被分配到第0代,
如上圖所示,當第0代的空間滿時,垃圾回收器啓動回收,不可達對象(上圖C、E)會被回收,存活的對象被歸爲第1代。
當第0代空間已滿,第1代也開始有不少不可達對象以致空間將滿時,這時兩代垃圾都將被回收。存活下來的對象(可達對象),第0代升爲第1代,第1代升爲第2代。
實際CLR的代回收機制更加「智能」,若是新建立的對象生存週期很短,第0代垃圾也會馬上被垃圾回收器回收(不用等空間分配滿)。另外,若是回收了第0代,發現還有不少對象「可達」,
並無釋放多少內存,就會增大第0代的預算至512KB,回收效果就會轉變爲:垃圾回收的次數將減小,但每次都會回收大量的內存。若是尚未釋放多少內存,垃圾回收器將執行
徹底回收(3代),若是仍是不夠,則會拋出「內存溢出」異常。
也就是說,垃圾回收器會根據回收內存的大小,動態的調整每一代的分配空間預算!達到自動優化!
垃圾回收背後有這樣一個基本的觀念:編程語言(大多數的)彷佛總能訪問無限的內存。而開發者能夠一直分配、分配再分配——像魔法同樣,取之不盡用之不竭。
.NET垃圾回收器的基本工做原理是:經過最基本的標記清除原理,清除不可達對象;再像磁盤碎片整理同樣壓縮、整理可用內存;最後經過分代算法實現性能最優化。