2017年9月8日 星期五

.NET C# 堆疊 Stack 與堆積 Heap 的記憶體耗盡測試


了解更多關於 [C# 程式設計手冊 





當我們在進行 .NET 應用程式開發過程中,經常會遇到這兩個錯誤訊息:StackOverflowException 與 OutOfMemoryException,他們分別表示了這個應用程式已經沒有足夠可用的記憶體,在這份練習中,將會展示出,造成這兩個例外異常的現象。
  • StackOverflowException
    當執行緒啟動執行的時候,預設會保留 1MB 的記憶體,這個記憶體就是所謂的堆疊 (Stack);而我們使用的資料型別若為 實值型別 (Value Type),這個時候,這些物件將會儲存在堆疊內;另外,當我們進行函式呼叫的時候,也會用到這個堆疊記憶體,只要結束函數執行,相關的記憶體會就自動歸還給堆疊,下次有其它函式要進行呼叫的時候,就可以重複繼續使用些堆疊記憶體。最後,.NET 記憶體回收機制,是不會來處理堆疊內的無參考記憶體的回收工作的。
  • OutOfMemoryException
    關於參考型別所實例化出來的執行個體(物件),當時的物件變數,實際上儲存的是堆積 (Heap) 上的某塊記憶體空間,也就是說,這個物件變數,儲存著一個參考,這個參考會指向堆積上的某個記憶體位置。當我們對於堆積上的物件不再使用的時候,也就是說,該物件沒有任何參考指向它,此時,.NET系統的 Garbage Collection 記憶體回收機制,會將這些沒有人參考的 Managed 記憶體進行回收,程式開發者不需要去處理這些記憶體回收的工作。因此,當堆積 Heap 的記憶體不夠用的時候,就會出現這個錯誤例外異常。
底下是我們要測式的兩種型別
  • MyStruct
    其型別為結構,屬於實值型別,也就是會使用堆疊記憶體。
  • MyLargeClass 與 MySmallClass
    其型別為類別,屬於參考型別,所產生的執行個體,將會儲存在堆積內。前者將會產生出大於 85000 bytes 的堆積空間的執行個體,而後者將會產生出小於 85000 bytes 的堆積空間的執行個體;我們將會使用這兩個類別所產生的執行個體,分別檢測 .NET 記憶體回收機制中,對於大型物件與小型物件的記憶體使用不足情況之測試。
    public struct MyStruct
    {
       public double X;
        public double Y;
    }

    public class MyLargeClass
    {
        // 這將會讓產生的執行個體會占用 > 85000 bytes 的記憶體大小
        public byte[] bytes = new byte[1024 * 86];
    }

    public class MySmallClass
    {
        // 這將會讓產生的執行個體會占用 < 85000 bytes 的記憶體大小
        public byte[] bytes = new byte[1024 * 20];
    }

進行測試

首先,我們來進行造成 StackOverflowException 例外異常的模擬測試,在這裡,我們設計一個遞迴呼叫函式,在這個函式裡面,會使用到一個結構物件,該結構物件將會耗用堆疊記憶體,由於該函式會無窮盡的呼叫自己,最終將會造成堆疊記憶體不足的情況發生。
        static void Main(string[] args)
        {
            long level = 0;
            Recursive(level);
        }

        private static void Recursive(long level)
        {
            MyStruct structObject;
            structObject.X = level;
            level++;
            Recursive(level);
        }
接著,我們來模擬堆積的記憶體耗盡的情境,由於 .NET 記憶體回收機制對於大型物件(大於 85000 Bytes)與小型物件的處理方式不同,所以,底下程式碼將會是進行大型物件的大量物件產生模擬;由於這些大物件產生之後,都會持續有參考到該物件,因此,GC是不會針對這些物件進行記憶體回收,最終會造成堆積記憶體耗盡,系統就會顯示 OutOfMemoryException 例外異常錯誤訊息。
            List<MyLargeClass> listObject = new List<MyLargeClass>();
            for (int i = 0; i < 1000 * 1000; i++)
            {
                listObject.Add(new MyLargeClass());
            }
        }

我們使用同樣的手法,只不過這次進行產生大量的小物件,並且這些小物件持續有參考指向該小物件,當然,最終還是會造成堆積記憶體耗盡,系統就會顯示 OutOfMemoryException 例外異常錯誤訊息。
            List<MySmallClass> listObject = new List<MySmallClass>();
            for (int i = 0; i < 1000 * 1000; i++)
            {
                listObject.Add(new MySmallClass());
            }
        }

了解更多關於 [C# 程式設計手冊 







沒有留言:

張貼留言