通過代碼實現 OutOfMemory

通過代碼實現 OutOfMemory

Intro

來嘗試寫一個發生 OutOfMemoryException 的代碼吧,開啓煞筆代碼第三篇 —— OutofMemory

OutOfMemory

OutOfMemory 顧名思義就是內存不足,在 .NET 中當內存不足的時候就會拋出 OutOfMemoryException 的異常。

想要觸發 OutOfMemoryException 就要滿足內存不足的條件,在 .NET Framework 中可能就只能一直分配內存直到內存不足,再沒有足夠的內存可以分配了,在 .NET Core 3.x 版本以後,微軟引入了一些 GC 的配置,我們可以通過這些配置來指定最大的 GC 內存,這樣我們就可以實現觸發 OutOfMemoryException 而不影響其他應用程序正常運行的目標了。在 .NET 5 中我們又可以更進一步更精細的控制 GC 使用的內存了,在 .NET 5 中我們可以針對每個堆(SOH/LOH/POH)來設置內存限制。

GC 堆內存限制配置

我們測試的示例使用限制 GC 堆大小 (Heap Limit) 的方式來限制應用程序的內存佔用以免影響到別的應用程序正常運行(該配置只針對 64 位電腦有效,現在的電腦應該大多都是64位吧)。

配置的方式有兩種,一種是通過環境變量來配置,一種是通過 runtime.config.json 來配置

通過環境變量配置 COMPlus_GCHeapHardLimit 爲要配置的內存大小,需要注意的是通過環境變量配置的時候指定的值需要是十六進制的值,通過 runtimeconfig.json 配置的時候是直接用十進制的數值

因爲我們只是想簡單的測試一下,不能影響別的應用程序,而且不能在代碼裏配置當前進程的環境變量,因爲進程啓動的時候 GC 的配置就已經加載好了,在代碼裏配置當前進程的環境變量來改變 GC 配置是不會生效的,所以我們選擇配置 runtimeconfig.json 來測試,在項目的 bin 目錄下可以找到 runtimeconfig.json 文件,我們修改這一個文件即可(使用 runtimeconfig.json 的時候需要注意先生成一下,然後再更新 runtimeconfig.json 文件)

測試配置如下,配置的 GC 堆的最大值是 1M(配置的不能太小,太小的話 CoreCLR 可能都會啓動失敗從而導致程序無法正常運行):

{
  "runtimeOptions": {
    "tfm": "netcoreapp3.1",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "3.1.0"
    },
    "configProperties": {
      "System.GC.HeapHardLimit": 1048576
    }
  }
}

測試代碼

測試代碼如下:

Console.ReadLine();
var bytes = GC.GetTotalAllocatedBytes();
Console.WriteLine($"AllocatedBytes: { bytes } bytes");
var list = new List<byte[]>();
try
{
    while (true)
    {
        list.Add(new byte[85000]);
    }
}
catch (OutOfMemoryException)
{
    Console.WriteLine(nameof(OutOfMemoryException));
    Console.WriteLine(list.Count);
    bytes = GC.GetTotalAllocatedBytes();
    Console.WriteLine($"AllocatedBytes: { bytes } bytes");
}
Console.ReadLine();

測試輸出如下:

上面的測試代碼使用的 byte 數組的長度是 85000 的原因是,當要分配的對象大於等於 85k(85000)時會直接分配到大對象堆中,正好可以測試一下。

我們使用微軟的 dotnet dump 診斷工具來測試一下

第一次 dump 是在 list 對象創建之前進行的,第二次 dump 是發生 OutOfMemory 之後的

從上面的 dump 結果可以看的出來,byte 數組的對象確實是分配在大對象堆(LOH)上的,幾乎所有的內存分配都在大對象堆中,有一些小對象從0 代升到了 1代。

More

上面的測試代碼使用的 byte 數組的長度是 85000 ,你測試的時候也可以使用更大的值,或者直接使用 int.MaxValue

在前面的 StackOverflow 文章中,有網友評論說,他們之前遇到的一個 StackOverflow 示例常常伴隨着 OutOfMemory ,遞歸和這種方式有點類似,都是要一直創建新的對象,分配新的內存。

除此之外,還有哪些更簡單的方式嗎?歡迎補充

References

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章