程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> .net垃圾回收-原理淺析,.net垃圾回收淺析

.net垃圾回收-原理淺析,.net垃圾回收淺析

編輯:C#入門知識

.net垃圾回收-原理淺析,.net垃圾回收淺析


本文引自:http://www.cnblogs.com/wilber2013/p/4357910.html

在開發.NET程序過程中,由於CLR中的垃圾回收(garbage collection)機制會管理已分配的對象,所以程序員就可以不用關注對象什麼時候釋放內存空間了。但是,了解垃圾回收機制還是很有必要的,下面我們就看看.NET垃圾回收機制的相關內容。

創建對象

在C#中,我們可以通過new關鍵字創建一個引用類型的對象,比如下面一條語句。New關鍵字創建了一個Student類型的對象,這個新建的對象會被存放在托管堆中,而這個對象的引用會存放在調用棧中。(對於引用類型可以查看,C#中值類型和引用類型)

Student s1 = new Student();

在C#中,當上面的Student對象被創建後,程序員就可以不用關心這個對象什麼時候被銷毀了,垃圾回收器將會在該對象不再需要時將其銷毀。

當一個進程初始化後,CLR就保留一塊連續的內存空間,這段連續的內存空間就是我們說的托管堆。.NET垃圾回收器會管理並清理托管堆,它會在必要的時候壓縮空的內存塊來實現優化,為了輔助垃圾回收器的這一行為,托管堆保存著一個指針,這個指針准確地只是下一個對象將被分配的位置,被稱為下一個對象的指針(NextObjPtr)。為了下面介紹垃圾回收機制,我們先詳細看看new關鍵字都做了什麼。

new關鍵字

當C#編譯器遇到new關鍵字時,它會在方法的實現中加入一條CIL newobj命令,下面是通過ILSpy看到的IL代碼。

IL_0001: newobj instance void GCTest.Student::.ctor()

其實,newobj指令就是告訴CLR去執行下列操作:

  • 計算新建對象所需要的內存總數
  • 檢查托管堆,確保有足夠的空間來存放新建的對象
    • 如果空間足夠,調用類型的構造函數,將對象存放在NextObjPtr指向的內存地址
    • 如果空間不夠,就會執行一次垃圾回收來清理托管堆(如果空間依然不夠,就會報出OutofMemoryException)
  • 最後,移動NextObjPtr指向托管堆下一個可用地址,然後將對象引用返回給調用者

按照上面的分析,當我們創建兩個Student對象的時候,托管堆就應該跟下圖一致,NextObjPtr指向托管堆新的可用地址。

托管堆的大小不是無限制的,如果我們一直使用new關鍵字來創建新的對象,托管堆就可能被耗盡,這時托管堆可以檢測到NextObjPtr指向的空間超過了托管堆的地址空間,就需要做一次垃圾回收了,垃圾回收器會從托管堆中刪除不可訪問的對象

應用程序的根

垃圾回收器是如何確定一個對象不再需要,可以被安全的銷毀?

這裡就要看一個應用程序根(application root)的概念。根(root)就是一個存儲位置其中保存著對托管堆上一個對象的引用,根可以屬性下面任何一個類別:

  • 全局對象和靜態對象的引用
  • 應用程序代碼庫中局部對象的引用
  • 傳遞進一個方法的對象參數的引用
  • 等待被終結(finalize,後面介紹)對象的引用
  • 任何引用對象的CPU寄存器

垃圾回收可以分為兩個步驟:

下面結合應用程序的根的概念,我們來看看垃圾回收這兩個步驟。

標記對象

在垃圾回收的過程中,垃圾回收器會認為托管堆中的所有對象都是垃圾,然後垃圾回收器會檢查所有的根。為此,CLR會建立一個對象圖,代表托管堆上所有可達對象。

假設托管堆中有A-G七個對象,垃圾回收過程中垃圾回收器會檢查所有的對象是否有活動根。這個例子的垃圾回收過程可以描述如下(灰色表示不可達對象):

代碼中很有可能多個對象中引用了同一個對象E,垃圾回收器只要檢測到對象E已經被標記過,則不再對對象E內所引用的對象進行檢測,這樣做有兩個目的:一是提高性能,二是避免無限循環

所有的根對象都檢查完之後,有標記的對象就是可達對象,未標記的對象就是不可達對象。

壓縮托管堆

繼續上面的例子,垃圾回收器將銷毀所有未被標記的對象,釋放這些垃圾對象所占的內存,再把可達對象移動到這裡以壓縮堆。

注意,在移動可達對象之後,所有引用這些對象的變量將無效,接著垃圾回收器要重新遍歷應用程序的所有根來修改它們的引用。在這個過程中如果各個線程正在執行,很可能導致變量引用到無效的對象地址,所以整個進程的正在執行托管代碼的線程是被掛起的。

經過了垃圾回收之後,所有的非垃圾對象被移動到一起,並且所有的非垃圾對象的指針也被修改成移動後的內存地址,NextObjPtr指向最後一個非垃圾對象的後面。

對象的代

當CLR試圖尋找不可達對象的時候,它需要遍歷托管堆上的對象。隨著程序的持續運行,托管堆可能越來越大,如果要對整個托管堆進行垃圾回收,勢必會嚴重影響性能。所以,為了優化這個過程,CLR中使用了"代"的概念,托管堆上的每一個對象都被指定屬於某個"代"(generation)。

"代"這個概念的基本思想就是,一個對象在托管堆上存在的時間越長,那麼它就更可能應該保留。托管堆中的對象可以被分為0、1、2三個代:

  • 0代:從沒有被標記為回收的新分配的對象
  • 1代:在上一次垃圾回收中沒有被回收的對象
  • 2代:在一次以上的垃圾回收後仍然沒有被回收的對象

下面還是通過一個例子看看代這個概念(灰色代表不可達對象):

通過前面的描述可以看到,分代可以避免每次垃圾回收都遍歷整個托管堆,這樣可以提高垃圾回收的性能。

System.GC

.NET類庫中提供了System.GC類型,通過該類型的一些靜態方法,可以通過編程的方式與垃圾回收器進行交互。

看一個簡單的例子:

class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Gender { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));

        Console.WriteLine("This OS has {0} object generations", GC.MaxGeneration);

        Student s = new Student { Id = 1, Name = "Will", Age = 28, Gender = "Male"};
        Console.WriteLine(s.ToString());

        Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

        GC.Collect();
        Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

        GC.Collect();
        Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

        Console.Read();
    }
}

程序的輸出為:

從這個輸出,我們也可以驗證代的概念,每次垃圾清理後,如果一個對象沒有被清理,那麼它的代就會提高。

強制垃圾回收

由於托管堆上的對象由垃圾管理器幫我們管理,所有我們不需要關心托管堆上對象的銷毀以及內存空間的回收。

但是,有些特殊的情況下,我們可能需要通過GC.Collect()強制垃圾回收:

在使用強制垃圾回收時,建議同時調用"GC.WaitForPendingFinalizers();",這樣可以確定在程序繼續執行之前,所有的可終結對象都必須執行必要的清除工作。但是要注意,GC.WaitForPendingFinalizers()會在回收過程中掛起調用的線程。

static void Main(string[] args)
{
    ……
    GC.Collect();
    GC.WaitForPendingFinalizers();
    ……
}

每一次垃圾回收過程都會損耗性能,所以要盡量避免通過GC.Collect()進行強制垃圾回收,除非遇到了真的需要強制垃圾回收的情況。

總結

本文介紹了.NET垃圾回收機制的基本工作過程,垃圾回收器通過遍歷托管堆上的對象進行標記,然後清除所有的不可達對象;在托管堆上的對象都被設置了一個代,通過了代這個概念,垃圾回收的性能得到了優化。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved