【问题标题】:System.OutOfMemoryException is thrown while still having free VM spaceSystem.OutOfMemoryException 在仍有可用 VM 空间时引发
【发布时间】:2013-09-09 10:29: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 左右(列数,如构造函数中所示)。因此,异常很可能发生在 CustomCell 分配中。 CustomCell 对象有很多字段,但总和肯定小于 1KB。 280MB 的空闲内存位于 64KB 到 14MB 的块中 - 所以应该有足够的空间来分配它。

【问题讨论】:

  • 也许问题是内存碎片。您有足够的可用内存,但缺少足够大的连续块来存储新数据。
  • 当然不是这样。我已经使用 Sysinternals 和 Visual Studio 的 VMMap 实用程序调查了应用程序内存 - 有高达 14Mb 的空闲块。失败的操作是创建一个小于 1KB 的对象。
  • OutOfMemory exception is thrown upon adding an item to the list.这里的列表是什么?
  • 用一段代码更新了问题。

标签: c# .net memory-management out-of-memory


【解决方案1】:

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

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

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

【讨论】:

  • 嗯,这很接近,但没有解释它。正如我所说,我有许多不同大小的块 - 总共 280 MB - 有足够的空间来放置 2MB 块。你确定段大小不是 16MB 吗?如果是的话,那就解释清楚了
  • 您一定错过了答案的“动态增长分配”部分。一开始只有2MB。当然它可以增长到 16MB。
  • 你错过了 280 MB 的部分 :) 你能描述一下这可能发生的确切场景吗?
  • 好的,我在别处找到了更详细的解释。你在高水平上还是对的,所以我接受你的回答。
【解决方案2】:

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

更多信息: OutOfMemoryException on declaration of Large Array

这对您来说也可能很有趣: 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-

【讨论】:

    【解决方案3】:

    编辑

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

    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)
    

    【讨论】:

      猜你喜欢
      • 2010-11-12
      • 1970-01-01
      • 2015-07-12
      • 2010-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多