在询问之前,我会做一个小小的免责声明: 是的,我知道虚拟内存,物理内存和工作集之间的差异。以下所有数字均指虚拟内存。
情况如下: 我们有一个32位的C#应用程序,它可以导入x86 C ++库(具有大量的本机依赖性,因此目前无法迁移到x64)。 该应用程序通过非托管组件加载大型数据集,然后尝试显示它(报告)。
但是,当数据集特别大时,在向列表添加项目时会抛出OutOfMemory异常,如下面的代码所示:
这里不足为奇。然而,令人惊讶的是,该应用程序仍有大约280MB的免费虚拟机。
在调试纯粹的非托管应用程序(C ++)时,情况从未如此 - 只有在没有剩余空闲虚拟机的情况下,或者如果没有足够大小的空闲地址空间块时,才能实现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-something(列数,如构造函数中所示)。因此,异常最有可能发生在CustomCell分配中。 CustomCell对象有很多字段,但总共不到1KB。 280MB的可用内存位于64KB到14MB的块中 - 因此应该有足够的空间来分配它。
答案 0 :(得分:2)
有大量高达14Mb的块。失败的操作是创建一个小于1KB的对象
14 MB绝对接近危险区域。 GC分配VM的方式与对象大小无关。 GC堆是从称为“段”的VM块创建的。程序启动时段大小为2 MB,但是当程序使用大量内存时,GC会动态增加新段的分配。显然你使用了大量的内存。您无法做任何事情来影响VM分配或避免VM地址空间碎片。
显然,您太接近32位进程的VM限制。您需要彻底修改代码,以便您可以少花钱。或者您需要在您的先决条件列表中放置64位操作系统。这可以为32位进程提供4千兆字节的VM地址空间。您需要额外的构建步骤才能利用它,如this answer中所述。
答案 1 :(得分:0)
修改
在添加代码之前编写。看起来你已经这样做了:
<击>
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)
击> <击> 撞击>
答案 2 :(得分:0)
当你有64位应用程序时,你必须小心,最大数组大小为2 GB,检查你是否在一个数组中加载你的报告表。