C#:Out of Memory异常

Tal*_*ode 65 c# out-of-memory

今天我的申请今天投了一个OutOfMemoryException.对我来说,这几乎是不可能的,因为我有4GB的RAM和大量的虚拟内存.当我尝试将现有集合添加到新列表时发生错误.

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);  
Run Code Online (Sandbox Code Playgroud)

根据我的理解,这里分配的内存不多,因为我的新列表应该包含的车辆已经存在于内存中.我不得不承认这Vehicle是一个非常复杂的课程,我试图一次性将大约50.000个项目添加到新列表中.但是由于Vehicle应用程序中的所有内容都来自一个只有200MB大小的数据库:我不知道OutOfMemoryException在这一点上可能会导致什么.

Thr*_*ent 82

3岁的话题,但我找到了另一个有效的解决方案.如果您确定有足够的可用内存,运行64位操作系统并仍然获得异常,请务必在项目属性中设置此选项 在此输入图像描述

  • `项目属性` -&gt; `Build` 选项卡 -&gt; 将 `Platform target` 更改为 `x64` (4认同)
  • 看不到图,有什么办法? (2认同)

Tud*_*dor 73

两点:

  1. 如果您运行的是32位Windows,则无法访问所有4GB,只有2GB.
  2. 不要忘记底层实现List是一个数组.如果您的内存严重碎片化,可能没有足够的连续空间来分配您List的内存,即使总共有足够的可用内存.

  • @CodyGray每个对象(数组)为2GB,而不是2GB. (24认同)
  • 您在64位Windows中也只有2 GB可用.这是.NET Framework的限制,而不仅仅是32位地址空间. (23认同)
  • @CodyGray,我认为2GB阵列限制不在新的.NET版本中. (5认同)
  • 可以使用`gcAllowVeryLargeObjects`配置设置来克服它.更多细节 - https://msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 (3认同)

小智 67

.Net4.5对象不再有2GB的限制.将此行添加到App.config

<runtime>
    <gcAllowVeryLargeObjects enabled="true" />    
</runtime>
Run Code Online (Sandbox Code Playgroud)

并且可以在不获取OutOfMemoryException的情况下创建非常大的对象

请注意它只适用于x64操作系统!

  • 做得很好.这对我有用,注意到必须将Build目标更改为x64. (2认同)

Ada*_*ley 14

与应用程序中的内存相比,存储在数据库中的数据非常不同

没有办法获得对象的确切大小,但您可以这样做:

GC.GetTotalMemory() 
Run Code Online (Sandbox Code Playgroud)

加载了一定数量的对象后,在加载列表时查看内存的更改量.

如果它是导致过多内存使用的列表,那么我们可以查看最小化它的方法.例如,为什么要首先将50,000个对象一次性加载到内存中.根据需要调用数据库不是最好的吗?

如果你看一下这里:http://www.dotnetperls.com/array-memory你还会看到.NET中的对象大于它们的实际数据.通用列表甚至比数组更重要.如果您的对象中有一个通用列表,那么它将变得更快.


Tao*_*Tao 8

OutOfMemoryException(在32位机器上)与碎片化一样经常与内存的实际硬限制一样 - 你会发现很多关于这一点,但这是我的第一篇google热门话题,简要讨论它: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);
Run Code Online (Sandbox Code Playgroud)

List<Vehicle> vList = new List<Vehicle>(50000);  
vList.AddRange(selectedVehicles);
Run Code Online (Sandbox Code Playgroud)

所有其他评论和答案仍然适用于整体设计决策 - 但这可能是一个快速解决方案.

注意(如下面的@Alex评论),如果selectedVehicles不是ICollection ,这只是一个问题.

  • 如果selecyedVehicles是一个集合,构造函数将分配正确的数组大小.无需通过AddRange. (3认同)

Bri*_*yer 7

我知道这是一个老问题,但由于没有一个答案提到大对象堆,这可​​能对发现这个问题的其他人有用......

.NET 中超过 85,000 字节的任何内存分配都来自大对象堆 (LOH),而不是普通的小对象堆。为什么这很重要?因为大对象堆没有被压缩。这意味着大型对象堆会变得碎片化,根据我的经验,这不可避免地会导致内存不足错误。

在最初的问题中,列表中有 50,000 个项目。列表内部使用数组,假设 32 位需要 50,000 x 4 字节 = 200,000 字节(如果是 64 位,则需要两倍)。因此内存分配来自大对象堆。

所以你对此能做些什么?

如果您使用的是 4.5.1 之前的 .net 版本,那么您所能做的就是意识到该问题并尝试避免它。因此,在这种情况下,您可以拥有一个车辆列表列表,而不是一个车辆列表,前提是列表中的元素不超过 18,000 个。这可能会导致一些丑陋的代码,但这是可行的解决方法。

如果您使用 .net 4.5.1 或更高版本,则垃圾收集器的行为会发生微妙的变化。如果您在要进行大量内存分配的位置添加以下行:

System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
Run Code Online (Sandbox Code Playgroud)

它将强制垃圾收集器压缩大对象堆 - 仅限下一次。

这可能不是最好的解决方案,但以下内容对我有用:

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

当然,这仅在您有可用的物理(或虚拟)内存时才有帮助。


小智 5

我的开发团队解决了这种情况:

我们将以下构建后脚本添加到.exe项目中,然后再次编译,将目标设置为x86并增加1.5 gb,并且将x64 Platform目标使用3.2 gb增加内存。我们的应用程序是32位。

相关网址:

脚本:

if exist "$(DevEnvDir)..\tools\vsvars32.bat" (
    call "$(DevEnvDir)..\tools\vsvars32.bat"
    editbin /largeaddressaware "$(TargetPath)"
)
Run Code Online (Sandbox Code Playgroud)