GC演算法與代齡

來源:互聯網
上載者:User

第一節. GC的演算法與工作方式

  1.演算法

  垃圾收集器的本質,就是跟蹤所有被引用到的對象,整理對象不再被引用的對象,回收相應的記憶體。

  這聽起來類似於一種叫做“引用計數(Reference Counting)”的演算法,然而這種演算法需要遍曆所有對象,並維護它們的引用情況,所以效率較低些,並且在出現“環引用”時很容易造成記憶體泄露。所以.Net中採用了一種叫做“標記與清除(Mark Sweep)”演算法來完成上述任務。

  “標記與清除”演算法,顧名思義,這種演算法有兩個本領:

  “標記”本領——垃圾的識別:從應用程式的root出發,利用相互參考關聯性,遍曆其在Heap上動態分配的所有對象,沒有被引用的對象不被標記,即成為垃圾;存活的對象被標記,即維護成了一張“根-對象可達圖”。

  其實,CLR會把對象關係看做“樹圖”,無疑,瞭解資料結構的同學都知道,有了“樹圖”的概念,會加快遍曆對象的速度。

  檢測、標記對象引用,是一件很有意思的事情,有很多方法可以做到,但是只有一種是效率最優的,.Net中是利用棧來完成的,在不斷的入棧與出棧中完成檢測:先在樹圖中選擇一個需要檢測的對象,將該對象的所有引用壓棧,如此反覆直到棧變空為止。棧變空意味著已經遍曆了這個局部根(或者說是樹圖中的節點)能夠到達的所有對象。樹圖節點範圍包括局部變數(實際上局部變數會很快被回收,因為它的範圍很明顯、很好控制)、寄存器、靜態變數,這些元素都要重複這個操作。一旦完成,便逐個對象地檢查記憶體,沒有標記的對象變成了垃圾。

“清除”本領——回收記憶體:啟用Compact演算法,對記憶體中存活的對象進行移動,修改它們的指標,使之在記憶體中連續,這樣閒置記憶體也就連續了,這就解決了記憶體片段問題,當再次為新對象分配記憶體時,CLR不必在充滿片段的記憶體中尋找適合新對象的記憶體空間,所以分配速度會大大提高。但是大對象(large object heap)除外,GC不會移動一個記憶體中巨無霸,因為它知道現在的CPU不便宜。通常,大對象具有很長的生存期,當一個大對象在.NET託管堆中產生時,它被分配在堆的一個特殊部分中,移動大對象所帶來的開銷超過了整理這部分堆所能提高的效能。

  Compact演算法除了會提高再次分配記憶體的速度,如果新分配的對象在堆中位置很緊湊的話,快取的效能將會得到提高,因為一起分配的對象經常被一起使用(程式的局部性原理),所以為程式提供一段連續空白的記憶體空間是很重要的。

  2.代齡(Generation)

  代齡就是對Heap中的對象按照存在時間長短進行分代,最短的分在第0代,最長的分在第2代,第2代中的對象往往是比較大的。Generation的層級與FrameWork版本有關,可以通過調用GC.MaxGeneration得知。

  通常,GC會優先收集那些最近分配的對象(第0代),這與作業系統經典記憶體換頁演算法“最近最少使用”演算法如出一轍。但是,這並不代表GC只收集最近分配的對象,通常,.Net GC將堆空間按對象的生存期長短分成3代:新分配的對象在第0代(0代空間最大長度通常為256K),按地址順序分配,它們通常是一些局部變數;第1代(1代空間最大長度通常為2 MB)是經過0代垃圾收集後仍然駐留在記憶體中的對象,它們通常是一些如表單,按鈕等對象;第2代是經曆過幾次垃圾收集後仍然駐留在記憶體中的對象,它們通常是一些應用程式物件。

  當記憶體吃緊時(例如0代對象充滿),GC便被調入執行引擎——也就是CLR——開始對第0代的空間進行標記與壓縮工作、回收工作,這通常小於1毫秒。如果回收後記憶體依然吃緊,那麼GC會繼續回收第1代(回收操作通常小於10毫秒)、第2代,當然GC有時並不是按照第0、1、2代的順序收集垃圾的,這取決於運行時的情況,或是手動調用GC.Collect(i)指定回收的代。當對第2代回收後任然無法獲得足夠的記憶體,那麼系統就會拋出OutOfMemoryException異常

  當經過幾次GC過後,0代中的某個對象仍然存在,那麼它將被移動到第1代。同理,第1、2代也按同樣的邏輯運行。

  這裡還要說的是,GC Heap中代的數量與容量,都是可變的(這由一個“策略引擎”控制,在第二節中,會介紹到“策略引擎”), 以下代碼結合Windbg可以說明這個問題,以下代碼中,可以通過單擊按鈕“button1”,不斷的分配記憶體,而後獲得對象“a”的代齡情況,並且在Form載入時也會獲得“a”的代齡。

Code
public partial class Form1 : Form
{
private string a = new string('a',1);
public Form1()
        {
            InitializeComponent();
        }
private void button1_Click(object sender, EventArgs e)
        {
            a = new string('a', 900000);
            label1.Text = GC.GetGeneration(a).ToString();
        }
private void Form1_Load(object sender, EventArgs e)
        {
            label1.Text = GC.GetGeneration(a).ToString();
        }
}

  程式剛載入時,“a”的代齡為第0代,通過windbg我們還獲得了以下資訊:

  可以看出,GC堆被分成了兩個段,三代,每代起始地址十進位差值為12。

  點擊數次“button1”按鈕後,“a”的代齡升為第2代,通過windbg我們又獲得了以下資訊:

  這裡要注意一個很關鍵的地方,就是各代的起始(generation x starts at)十進位地址差值不再是12,0代與1代差為98904,1代與2代差為107908,這說明代的大小隨程式運行在改變,並且GC heap的大小也有變化。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.