簡體   English   中英

C#:內存不足異常

[英]C# : Out of Memory exception

今天我的應用程序拋出了OutOfMemoryException 對我來說這幾乎是不可能的,因為我也有 4GB RAM 和大量虛擬內存。 當我嘗試將現有集合添加到新列表時發生錯誤。

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);  

據我了解,這里分配的內存不多,因為我的新列表應該包含的車輛已經存在於內存中。 我不得不承認Vehicle是一個非常復雜的類,我試圖一次將大約 50.000 項添加到新列表中。 但是由於應用程序中的所有Vehicle都來自一個只有 200MB 大小的數據庫,所以我不知道此時可能導致OutOfMemoryException的原因。

3歲的話題,但我找到了另一個可行的解決方案。 如果您確定有足夠的可用內存、運行 64 位操作系統並且仍然出現異常,請轉到Project properties -> Build選項卡並確保將x64設置為Platform target

在此處輸入圖像描述

兩點:

  1. 如果您運行的是 32 位 Windows,您將無法訪問所有 4GB,只有 2GB。
  2. 不要忘記List的底層實現是一個數組。 如果您的內存嚴重碎片化,則可能沒有足夠的連續空間來分配您的List ,即使您總共有足夠的可用內存。

.Net4.5對對象不再有 2GB 的限制。 將此行添加到App.config

<runtime>
    <gcAllowVeryLargeObjects enabled="true" />    
</runtime>

並且可以創建非常大的對象而不會出現OutOfMemoryException

請注意,它僅適用於 x64 操作系統!

與應用程序中的內存相比,存儲在數據庫中的數據非常不同。

沒有辦法獲得對象的確切大小,但您可以這樣做:

GC.GetTotalMemory() 

在加載了一定數量的對象后,看看你的內存在加載列表時發生了多少變化。

如果是列表導致了過多的內存使用,那么我們可以尋找將其最小化的方法。 例如,為什么您首先要一次將 50,000 個對象全部加載到內存中。 根據需要調用數據庫不是最好嗎?

如果您看一下:http: //www.dotnetperls.com/array-memory ,您還會看到 .NET 中的對象大於它們的實際數據。 通用列表比數組更占用內存。 如果您的對象中有一個通用列表,那么它會增長得更快。

OutOfMemoryException(在 32 位機器上)與內存的實際硬限制一樣經常與碎片有關 - 您會發現很多關於此的內容,但這是我的第一個谷歌點擊簡要討論它:http: //blogs.msdn.com/b /joshwil/archive/2005/08/10/450202.aspx (@Anthony Pegram 在上面的評論中提到了同樣的問題)。

也就是說,對於上面的代碼,還有另一種可能性:當您對 List 使用“IEnumerable”構造函數時,您可能不會向對象提供有關您傳遞的集合大小的任何提示到 List 構造函數。 如果您傳遞的對象不是一個集合(沒有實現ICollection接口),那么在幕后 List 實現將需要增長幾次(或多次),每次都留下一個太小的需要進行垃圾收集的數組。 垃圾收集器可能無法足夠快地到達那些丟棄的數組,並且您會得到錯誤。

最簡單的解決方法是使用List(int capacity)構造函數來告訴框架要分配的后備數組大小(例如,即使您正在估計並只是猜測“50000”),然后使用AddRange(IEnumerable collection)方法來實際填充您的列表。

所以,如果我是對的,最簡單的“修復”:替換

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);

List<Vehicle> vList = new List<Vehicle>(50000);  
vList.AddRange(selectedVehicles);

所有其他評論和答案仍然適用於整體設計決策 - 但這可能是一個快速修復。

注意(正如@Alex 在下面評論的那樣),如果selectedVehicles不是 ICollection,這只是一個問題。

我的開發團隊解決了這種情況:

我們將以下 Post-Build 腳本添加到 .exe 項目中並再次編譯,將目標設置為 x86 並增加 1.5 gb,並且 x64 平台目標使用 3.2 gb 增加內存。 我們的應用程序是 32 位的。

相關網址:

腳本:

if exist "$(DevEnvDir)..\tools\vsvars32.bat" (
    call "$(DevEnvDir)..\tools\vsvars32.bat"
    editbin /largeaddressaware "$(TargetPath)"
)

我知道這是一個老問題,但由於沒有一個答案提到大對象堆,這可​​能對發現這個問題的其他人有用......

.NET 中超過 85,000 字節的任何內存分配都來自大對象堆 (LOH),而不是普通的小對象堆。 為什么這很重要? 因為大對象堆沒有被壓縮。 這意味着大型對象堆會變得碎片化,根據我的經驗,這不可避免地會導致內存不足錯誤。

在原始問題中,列表中有 50,000 個項目。 在內部,列表使用數組,並假設 32 位需要 50,000 x 4bytes = 200,000 字節(如果是 64 位,則為兩倍)。 所以內存分配來自大對象堆。

所以你對此能做些什么?

如果您使用的是 4.5.1 之前的 .net 版本,那么您所能做的就是了解該問題並盡量避免它。 因此,在這種情況下,如果沒有一個列表包含超過 18,000 個元素,那么您可以擁有一個車輛列表,而不是車輛列表。 這可能會導致一些丑陋的代碼,但它是可行的解決方法。

如果您使用的是 .net 4.5.1 或更高版本,則垃圾收集器的行為發生了微妙的變化。 如果您在即將進行大內存分配的位置添加以下行:

System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;

它將強制垃圾收集器壓縮大對象堆 - 僅限下一次。

這可能不是最好的解決方案,但以下對我有用:

int tries = 0;
while (tries++ < 2)
{
  try 
  {
    . . some large allocation . .
    return;
  }
  catch (System.OutOfMemoryException)
  {
    System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
    GC.Collect();
  }
}

當然,這僅在您有可用的物理(或虛擬)內存時才有幫助。

您不應該嘗試一次帶入所有列表,數據庫中元素的大小與它放入內存中的元素大小不同。 如果你想處理元素,你應該使用 for each 循環並利用實體框架延遲加載,這樣你就不會一次將所有元素都放入內存。 如果您想顯示列表,請使用分頁(.Skip() 和 .take())

隨着 .Net 的發展,他們添加新的 32 位配置的能力也在不斷增強,這似乎讓每個人都大吃一驚。

如果您使用的是 .Net Framework 4.7.2,請執行以下操作:

轉到項目屬性

建造

取消選中“首選 32 位”

干杯!

雖然 GC 壓縮小對象堆作為消除內存漏洞的優化策略的一部分,但出於性能原因,GC 從不壓縮大對象堆**(對於大對象(大於 85KB 大小),壓縮成本太高) )**。 因此,如果您在 x86 系統中運行使用許多大型對象的程序,您可能會遇到 OutOfMemory 異常。 如果您在 x64 系統中運行該程序,您可能會有一個碎片堆。

我為如何在 Rider 中獲得 64 位工作而苦苦掙扎。 Rider 上的開發人員需要手動添加解決方案配置。 這對我來說是固定的。

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM