0

在问之前,我先做一个小小的免责声明:是的,我知道虚拟内存、物理内存和工作集之间的区别。以下所有数字均指虚拟内存。

情况如下:我们有一个 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 左右(列数,如构造函数中所示)。因此,异常很可能发生在 CustomCell 分配中。CustomCell 对象有很多字段,但总和肯定小于 1KB。280MB 的空闲内存位于 64KB 到 14MB 的块中——所以应该有足够的空间来分配它。

4

3 回答 3

2

有高达 14Mb 的空闲块。失败的操作是创建一个小于 1KB 的对象

14 MB 绝对接近危险区。GC 分配 VM 的方式与对象大小无关。GC 堆是由称为“段”的 VM 块创建的。程序启动时段大小为 2 MB,但当程序使用大量内存时,GC 会动态增加新段的分配。显然你使用了大量的内存。您无法采取任何措施来影响 VM 分配或避免 VM 地址空间碎片。

显然,您离 32 位进程的 VM 限制太近了。你需要彻底修改你的代码,这样你就可以用更少的钱来做。或者,您需要将 64 位操作系统放在先决条件列表中。它可以为 32 位进程提供 4 GB 的 VM 地址空间。您需要一个额外的构建步骤来利用它,如this answer中所述。

于 2013-09-05T15:36:38.453 回答
0

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

更多信息: 声明大数组时出现 OutOfMemoryException

这也可能对您感兴趣: http ://social.msdn.microsoft.com/Forums/en-US/1a12abaa-50bd-4d28-b3c1-9de06a1488e9/how-to-create-an-extremely-large-arrayobject- 2-gb-不使用锯齿状阵列-

于 2013-09-05T14:37:31.980 回答
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)

于 2013-09-05T14:36:24.683 回答