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的块中 - 因此应该有足够的空间来分配它。
有高达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-