2017年11月11日 星期六

.NET Core 與 .NET Framework 在 GC 釋放物件的不同處理方式

在這裡,我們將會比較 .NET Core 與 .NET Framework 中的 CLR 對於 GC 的處理運作差異點。我們會針對這兩個 .NET 平台的 Debug / Release 模式下,跑同樣的測試程式,看看會有甚麼樣的結果。

可以追蹤該別現在多少物件正在使用中

我們首先建立這個 CountObject 類別,這個類別有個靜態屬性 TotalObjects,這個屬性會當該類別的建構函式被呼叫的時候(也就是,用戶端有使用 new 運算子要建立一個新的物件),就會把這個靜態屬性 TotalObjects加一,表示,現在該類別在 CLR 中,有一個物件生成了。
而當 CLR (通用語言執行階段) 進行 GC (記憶體回收程序) 過程中,若發現到有物件沒有被任何物件或者變數參考使用到,此時,GC 就會進行該物件的記憶體回收工作;而由於我們在這個類別中,有加入解構函式 ( ~CountObject() )的宣告,因此,當有物件要被回收的時候,該類別的解構函式就會被呼叫到,而在解構函式內,每被呼叫一次,就會自動將靜態屬性 TotalObjects減一,表示該這個類別在 CLR 中的物件又減少了一個。
public class CountObject
{
    /// <summary>
    /// 記錄下現在記憶體中,總共存在這多少個物件
    /// </summary>
    private static int _TotalObjects;

    public static int TotalObjects
    {
        get { return _TotalObjects; }
        set { _TotalObjects = value; }
    }

    string ObjectName;

    public CountObject()
    {
        // 若有該類別的物件產生,則計數器會加一
        CountObject.TotalObjects += 1;
        Console.WriteLine($"現在總共有 {CountObject.TotalObjects} 物件");
    }

    public CountObject(string objectName) : this()
    {
        Console.WriteLine($"有新的物件要產生 {objectName}");
        ObjectName = objectName;
    }

    ~CountObject()
    {
        Console.WriteLine($"有物件要回收 {ObjectName}");
        // 若有物件被回收,則該計數器會減一
        CountObject.TotalObjects -= 1;
    }
}

測試步驟

接下來的測試動作,將會分成

建立六個物件,並且執行 GC,看看剩下多少物件

我們首先呼叫 new 運算子,建立四個 CountObject 類別物件,不過,這四個物件,是沒有指定給任何物件變數(也就是,只要 GC 一執行,這四個物件一定會被回收的)。
接下來,建立兩個 CountObject 類別物件,但是,這裡將會指定到兩個變數中,也就是,這兩個物件將會被參考使用中。
接著,我們將會強制執行 GC 運行,並且等候一段時間,讓沒有被參考到的物件,可以順利被回收(想要了解這方面的進階資訊,請參考 GC 的運作模式相關文件)。
最後,我們將會顯示出靜態屬性 CountObject.TotalObjects 的值,看看現在還有多少物件存在於記憶體中。
Console.WriteLine("現在要產生六個物件");
new CountObject("物件1");
new CountObject("物件2");
new CountObject("物件3");
new CountObject("物件4");
var fooObject = new CountObject("物件5");
var fooTempObject = new CountObject("物件6");
Console.WriteLine($"Total Objects is {CountObject.TotalObjects}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

// 進行記憶體回收工作
Console.WriteLine("進行記憶體回收工作");
GC.Collect(2, GCCollectionMode.Forced);

// 將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Console.WriteLine("將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收");
Thread.Sleep(3000);
Console.WriteLine($"Total Objects is {CountObject.TotalObjects}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

在這個測試程序中,將會把 fooTempObject 物件變數設定為 null,也就是原先該變數指向的物件,現在就沒有任何人正在參考與使用中,另外一個物件,是有 fooObject 變數指向它。
此時,我們將會強制執行 GC 運行,並且等候一段時間,讓沒有被參考到的物件,可以順利被回收(想要了解這方面的進階資訊,請參考 GC 的運作模式相關文件)。
最後,我們將會顯示出靜態屬性 CountObject.TotalObjects 的值,看看現在還有多少物件存在於記憶體中。
Console.WriteLine("將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收");
fooTempObject = null;
// 進行記憶體回收工作
Console.WriteLine("進行記憶體回收工作");
GC.Collect(2, GCCollectionMode.Forced);

// 將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Console.WriteLine("將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收");
Thread.Sleep(3000);
Console.WriteLine($"Total Objects is {CountObject.TotalObjects}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

在 .NET Core 平台下進行測試 (Debug 模式)

建立六個物件,並且執行 GC,看看剩下多少物件

輸出結果
現在要產生六個物件
現在總共有 1 物件
有新的物件要產生 物件1
現在總共有 2 物件
有新的物件要產生 物件2
現在總共有 3 物件
有新的物件要產生 物件3
現在總共有 4 物件
有新的物件要產生 物件4
現在總共有 5 物件
有新的物件要產生 物件5
現在總共有 6 物件
有新的物件要產生 物件6
Total Objects is 6
Press any key for continuing...
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Total Objects is 6
Press any key for continuing...

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

輸出結果
將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Total Objects is 6
Press any key for continuing...

在 .NET Core 平台下進行測試 (Release 模式)

建立六個物件,並且執行 GC,看看剩下多少物件

輸出結果
現在要產生六個物件
現在總共有 1 物件
有新的物件要產生 物件1
現在總共有 2 物件
有新的物件要產生 物件2
現在總共有 3 物件
有新的物件要產生 物件3
現在總共有 4 物件
有新的物件要產生 物件4
現在總共有 5 物件
有新的物件要產生 物件5
現在總共有 6 物件
有新的物件要產生 物件6
Total Objects is 6
Press any key for continuing...
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
有物件要回收 物件6
有物件要回收 物件5
有物件要回收 物件4
有物件要回收 物件3
有物件要回收 物件2
有物件要回收 物件1
Total Objects is 0
Press any key for continuing...

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

輸出結果
將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Total Objects is 0
Press any key for continuing...

在 .NET Framework 平台下進行測試 (Debug 模式)

建立六個物件,並且執行 GC,看看剩下多少物件

輸出結果
現在要產生六個物件
現在總共有 1 物件
有新的物件要產生 物件1
現在總共有 2 物件
有新的物件要產生 物件2
現在總共有 3 物件
有新的物件要產生 物件3
現在總共有 4 物件
有新的物件要產生 物件4
現在總共有 5 物件
有新的物件要產生 物件5
現在總共有 6 物件
有新的物件要產生 物件6
Total Objects is 6
Press any key for continuing...
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
有物件要回收 物件4
有物件要回收 物件3
有物件要回收 物件2
有物件要回收 物件1
Total Objects is 2
Press any key for continuing...

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

輸出結果
將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
有物件要回收 物件6
Total Objects is 1
Press any key for continuing...

在 .NET Framework 平台下進行測試 (Release 模式)

建立六個物件,並且執行 GC,看看剩下多少物件

輸出結果
現在要產生六個物件
現在總共有 1 物件
有新的物件要產生 物件1
現在總共有 2 物件
有新的物件要產生 物件2
現在總共有 3 物件
有新的物件要產生 物件3
現在總共有 4 物件
有新的物件要產生 物件4
現在總共有 5 物件
有新的物件要產生 物件5
現在總共有 6 物件
有新的物件要產生 物件6
Total Objects is 6
Press any key for continuing...
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
有物件要回收 物件6
有物件要回收 物件5
有物件要回收 物件4
有物件要回收 物件3
有物件要回收 物件2
有物件要回收 物件1
Total Objects is 0
Press any key for continuing...

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

輸出結果
將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Total Objects is 0
Press any key for continuing...

差異比較

在 Debug 模式下,在第一階段的時候
  • .NET Core
    並不會回收任何沒有被參考到的物件
  • .NET Framework
    會將沒有被參考到到的物件,自動回收
在 Relase 模式下,這兩個 .NET Core 與 .NET Framework 平台的運作方式都一樣。

沒有留言:

張貼留言