System.OutOfMemoryException 在仍有可用 VM 空间时引发

本文关键字:空间 VM OutOfMemoryException System | 更新日期: 2023-09-27 18:30:23

在询问之前,我会做一个小的免责声明:是的,我知道虚拟内存、物理内存和工作集之间的差异。下面的所有数字都是指虚拟内存。

情况如下:我们有一个 32 位 C# 应用程序,它导入 x86 C++库(具有大量本机依赖项,因此目前无法迁移到 x64)。应用通过非托管组件加载大型数据集,然后尝试显示它(报表)。

但是,当数据集特别大时,将项添加到列表时会引发 OutOfMemory 异常,如下面的代码所示:

这里不足为奇。但是,令人惊讶的是,该应用程序仍然具有大约280MB的可用VM。

调试纯非托管应用程序 (C++) 时,情况并非如此 - 仅当没有剩余可用 VM 或没有足够大小的可用地址空间块时,才能实现bad_alloc。

因此,问题 - 这可能是什么原因?我了解如何解决这个问题 - 非托管组件确实会消耗大量内存,并产生大量碎片 - 但是 OutOfMemoryException 出现这么早的原因是什么?

有问题的代码如下所示:

List<Cell> r = new List<Cell>(cols);
for (int j = 0; j < cols; j++)
{
    r.Add(new CustomCell()); // The exception is thrown on this line
}

异常时刻,列表(在一次出现中)有 85 个项目,其容量为 200 多个(列数,如构造函数所示)。因此,异常最有可能发生在自定义单元格分配中。自定义单元格对象有许多字段,但总数肯定小于 1KB。280MB的可用内存位于从64KB到14MB的块中 - 因此应该有足够的空间来分配它。

System.OutOfMemoryException 在仍有可用 VM 空间时引发

有高达14Mb的块可用。失败的操作是创建总大小小于 1KB 的对象

14 MB绝对接近危险区域。 GC 分配 VM 的方式与对象大小无关。 GC 堆是从称为"分段"的虚拟机块创建的。 当程序启动时,段大小为 2 MB,但当程序使用大量内存时,GC 会动态增加新段的分配。 显然,您使用了大量内存。 无法执行任何操作来影响 VM 分配或避免 VM 地址空间碎片。

显然,您太接近 32 位进程的 VM 限制了。 您需要大幅修改代码,以便使用更少的代码。 或者,您需要将 64 位操作系统放在先决条件列表中。 这可以为 32 位进程提供 4 GB 的 VM 地址空间。 您需要一个额外的构建步骤来利用它,如本答案中所述。

编辑

在添加代码之前编写。看起来您已经在这样做了:

List实例具有容量。如果超出容量,则会调用一个不断增长的算法,使列表的容量加倍。

//decompiled from List<T>
private void EnsureCapacity(int min)
{
  if (this._items.Length >= min)
    return;
  int num = this._items.Length == 0 ? 4 : this._items.Length * 2;
  if ((uint) num > 2146435071U)
    num = 2146435071;
  if (num < min)
    num = min;
  this.Capacity = num; //causes a copying of src array to a new array
}

这可能会导致意外的内存分配。如果您能够预测列表的最终大小,请预先分配它并避免加倍:

var myList = new List<SomeType>(expectedCapacity)

当您有 64 位应用程序时,您还必须注意最大数组大小为 2 GB,请检查您是否在数组中加载报告表。

更多信息:内存不足声明大数组时的异常

这也可能对您来说是穿插的:http://social.msdn.microsoft.com/Forums/en-US/1a12abaa-50bd-4d28-b3c1-9de06a1488e9/how-to-create-an-extremely-large-arrayobject-2-gb-without-using-jagged-arrays-