【问题标题】:'System.OutOfMemoryException' was thrown when there is still plenty of memory free当仍有大量可用内存时引发“System.OutOfMemoryException”
【发布时间】:2010-11-12 07:51:28
【问题描述】:

这是我的代码:

int size = 100000000;
double sizeInMegabytes = (size * 8.0) / 1024.0 / 1024.0; //762 mb
double[] randomNumbers = new double[size];

例外: 引发了“System.OutOfMemoryException”类型的异常。

我在这台机器上有 4GB 内存 2.5GB 空闲 当我开始运行时,PC 上显然有足够的空间来处理 100000000 个随机数的 762mb。给定可用内存,我需要存储尽可能多的随机数。当我投入生产时,盒子上会有 12GB 的空间,我想利用它。

CLR 是否将我限制在默认的最大内存开始?以及如何要求更多?

更新

我认为如果问题是由于内存碎片造成的,我认为将其分成更小的块并逐渐增加我的内存需求会有所帮助,但它没有我无法通过ArrayList 的总大小为 256mb,无论我如何调整 blockSize

private static IRandomGenerator rnd = new MersenneTwister();
private static IDistribution dist = new DiscreteNormalDistribution(1048576);
private static List<double> ndRandomNumbers = new List<double>();

private static void AddNDRandomNumbers(int numberOfRandomNumbers) {
    for (int i = 0; i < numberOfRandomNumbers; i++) {
      ndRandomNumbers.Add(dist.ICDF(rnd.nextUniform()));                
  }
}

从我的主要方法:

int blockSize = 1000000;

while (true) {
  try
  {
    AddNDRandomNumbers(blockSize);                    
  }
  catch (System.OutOfMemoryException ex)
  {
    break;
  }
}            
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;

【问题讨论】:

  • 我建议重新构建您的应用程序,这样您就不必使用太多内存。你在做什么需要同时在内存中存储一​​亿个数字?
  • 你没有禁用你的页面文件或类似的傻事,对吧?
  • @EricLippert,我在处理 P 与 NP 问题 (claymath.org/millenium-problems/p-vs-np-problem) 时遇到了这个问题。你有减少工作内存使用的建议吗? (例如,在硬盘上序列化和存储大块数据,使用 C++ 数据类型等)
  • @bosit 这是一个问答网站。如果您对实际代码有特定的技术问题,请将其作为问题发布。
  • @bostIT 您评论中 P 与 NP 问题的链接不再有效。

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


【解决方案1】:

您可能想阅读以下内容:Eric Lippert 的““Out Of Memory” Does Not Refer to Physical Memory”。

简而言之,非常简单,“内存不足”并不意味着可用内存量太小。最常见的原因是在当前地址空间中,没有足够大的连续内存部分来满足所需的分配。如果您有 100 个块,每个块 4 MB 大,那么当您需要一个 5 MB 块时,这将无济于事。

要点:

  • 在我看来,我们称之为“进程内存”的数据存储最好可视化为磁盘上的海量文件
  • RAM 可以看作仅仅是一种性能优化
  • 程序消耗的虚拟内存总量与其性能实际上并没有太大关系
  • “内存不足”很少会导致“内存不足”错误。它不会导致错误,而是会导致性能下降,因为存储实际上在磁盘上这一事实的全部成本突然变得相关。

【讨论】:

  • “如果你有 100 个块,每个 4 MB 大,当你需要一个 5 MB 的块时,这对你没有帮助” - 我认为这会更好稍作修正:“如果你有 100 个 “洞” 块”.
【解决方案2】:

检查您构建的是 64 位进程,而不是 32 位进程,这是 Visual Studio 的默认编译模式。为此,请右键单击您的项目,属性 -> 构建 -> 平台目标:x64。与任何 32 位进程一样,以 32 位编译的 Visual Studio 应用程序的虚拟内存限制为 2GB。

64 位进程没有此限制,因为它们使用 64 位指针,因此它们的理论最大地址空间(其虚拟内存的大小)为 16 艾字节 (2^64)。实际上,Windows x64 将进程的虚拟内存限制为 8TB。那么内存限制问题的解决方案就是编译成64位。

但是,.NET 中的对象大小仍然限制为 2GB,默认情况下。您将能够创建多个组合大小大于 2GB 的数组,但默认情况下您不能创建大于 2GB 的数组。希望如果您仍想创建大于 2GB 的数组,可以通过将以下代码添加到 app.config 文件来实现:

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>

【讨论】:

    【解决方案3】:

    您没有连续的内存块来分配 762MB,您的内存是碎片化的,分配器找不到足够大的孔来分配所需的内存。

    1. 您可以尝试使用 /3GB(正如其他人建议的那样)
    2. 或切换到 64 位操作系统。
    3. 或者修改算法,使其不需要大量内存。可能会分配一些较小(相对)的内存块。

    【讨论】:

      【解决方案4】:

      您可能已经知道,问题在于您正在尝试分配一个大的连续内存块,由于内存碎片而无法正常工作。如果我需要做你正在做的事情,我会做以下事情:

      int sizeA = 10000,
          sizeB = 10000;
      double sizeInMegabytes = (sizeA * sizeB * 8.0) / 1024.0 / 1024.0; //762 mb
      double[][] randomNumbers = new double[sizeA][];
      for (int i = 0; i < randomNumbers.Length; i++)
      {
          randomNumbers[i] = new double[sizeB];
      }
      

      然后,要获取特定索引,您可以使用 randomNumbers[i / sizeB][i % sizeB]

      如果您总是按顺序访问值,另一个选项可能是使用the overloaded constructor 来指定种子。这样,您将获得一个半随机数(如 DateTime.Now.Ticks)将其存储在一个变量中,然后当您开始浏览列表时,您将使用原始种子创建一个新的 Random 实例:

      private static int randSeed = (int)DateTime.Now.Ticks;  //Must stay the same unless you want to get different random numbers.
      private static Random GetNewRandomIterator()
      {
          return new Random(randSeed);
      }
      

      值得注意的是,虽然 Fredrik Mörk 的回答中链接的博客表明问题通常是由于缺少 地址空间,但它没有列出许多其他问题,例如 2GB CLR 对象大小限制(在 ShuggyCoUk 在同一博客上的评论中提到),掩盖了内存碎片,并且没有提及页面文件大小的影响(以及如何使用CreateFileMapping function 解决它)。

      2GB 限制意味着randomNumbers 必须小于 2GB。由于数组是类并且它们本身有一些开销,这意味着double 的数组需要小于 2^31。我不确定长度必须比 2^31 小多少,但Overhead of a .NET array? 表示 12 - 16 个字节。

      内存碎片与硬盘碎片非常相似。您可能有 2GB 的地址空间,但是当您创建和销毁对象时,值之间会有间隙。如果这些间隙对于您的大对象来说太小,并且无法请求额外的空间,那么您将获得System.OutOfMemoryException。例如,如果您创建 200 万个 1024 字节的对象,那么您使用的是 1.9GB。如果您删除地址不是 3 的倍数的每个对象,那么您将使用 0.6GB 的内存,但它将分散在地址空间中,其间有 2024 字节的开放块。如果您需要创建一个 0.2GB 的对象,您将无法执行此操作,因为没有足够大的块来容纳它并且无法获得额外的空间(假设是 32 位环境)。此问题的可能解决方案是使用较小的对象、减少存储在内存中的数据量或使用内存管理算法来限制/防止内存碎片。应该注意的是,除非您正在开发一个使用大量内存的大型程序,否则这不是问题。此外,在 64 位系统上可能会出现此问题,因为 Windows 主要受页面文件大小和系统 RAM 量的限制。

      由于大多数程序从操作系统请求工作内存并且不请求文件映射,因此它们将受到系统 RAM 和页面文件大小的限制。正如 Néstor Sánchez (Néstor Sánchez) 在博客上的评论中指出的那样,使用像 C# 这样的托管代码,您会受到 RAM/页面文件限制和操作系统的地址空间的限制。


      这比预期的要长得多。希望它可以帮助某人。我发布它是因为我遇到了 System.OutOfMemoryException 在具有 24GB RAM 的系统上运行 x64 程序,即使我的阵列只容纳 2GB 的东西。

      【讨论】:

      • “由于大多数程序从操作系统请求工作内存并且不请求文件映射” - 您是否碰巧有任何资源进一步解释这一点?当程序需要它们的工作集驻留在内存中(并且受 RAM 大小限制)与程序可以写入磁盘并受连续内存限制时(正如接受的答案的博客所建议的那样)
      • @uMdRupert 我已经有一段时间没有研究和写这个了,所以我没有任何额外的资源。您是否有机会阅读 [CreateFileMapping 函数] (msdn.microsoft.com/en-us/library/windows/desktop/aa366537.aspx) 链接?您可能还想阅读Paging
      【解决方案5】:

      我建议不要使用 /3GB Windows 启动选项。除了其他一切(对于一个表现不佳的应用程序来说这样做有点过头了,而且它可能无论如何也解决不了你的问题),它可能会导致很多不稳定。

      许多 Windows 驱动程序未使用此选项进行测试,因此其中相当多的驱动程序假定用户模式指针始终指向地址空间的较低 2GB。这意味着它们可能会因 /3GB 而严重崩溃。

      但是,Windows 通常会将 32 位进程限制为 2GB 地址空间。 但这并不意味着您应该期望能够分配 2GB!

      地址空间已经充满了各种分配的数据。有堆栈,所有加载的程序集,静态变量等等。无法保证任何地方都会有 800MB 的连续未分配内存。

      分配 2 400MB 块可能会更好。或 4 200MB 块。较小的分配更容易在碎片化的内存空间中找到空间。

      无论如何,如果您要将其部署到 12GB 的机器上,您需要将其作为 64 位应用程序运行,这样应该可以解决所有问题。

      【讨论】:

      • 将作业分成更小的块似乎也无助于查看我上面的更新。
      【解决方案6】:

      从 32 位更改为 64 位对我有用 - 如果您在 64 位电脑上并且不需要移植,值得一试。

      【讨论】:

        【解决方案7】:

        如果您需要如此大的结构,也许您可​​以使用内存映射文件。 这篇文章可能会有所帮助: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

        LP, 德让

        【讨论】:

          【解决方案8】:

          32 位 Windows 的进程内存限制为 2GB。其他人提到的 /3GB 引导选项将使这个 3GB 空间只剩下 1gb 供操作系统内核使用。实际上,如果您想轻松使用超过 2GB 的空间,则需要 64 位操作系统。这也解决了一个问题,即虽然您可能有 4GB 的物理 RAM,但视频卡所需的地址空间会使相当大的内存卡盘无法使用 - 通常约为 500MB。

          【讨论】:

            【解决方案9】:

            您可以尝试使用迭代器,而不是分配一个庞大的数组吗?这些是延迟执行的,这意味着值仅在 foreach 语句中请求时生成;你不应该以这种方式耗尽内存:

            private static IEnumerable<double> MakeRandomNumbers(int numberOfRandomNumbers) 
            {
                for (int i = 0; i < numberOfRandomNumbers; i++)
                {
                    yield return randomGenerator.GetAnotherRandomNumber();
                }
            }
            
            
            ...
            
            // Hooray, we won't run out of memory!
            foreach(var number in MakeRandomNumbers(int.MaxValue))
            {
                Console.WriteLine(number);
            }
            

            上面将生成任意数量的随机数,但仅在通过 foreach 语句要求时生成它们。这样你就不会耗尽内存。

            或者,如果您必须将它们全部放在一个位置,请将它们存储在文件中而不是内存中。

            【讨论】:

            • Iteresting 方法,但我需要在我的应用程序其余部分的任何空闲时间尽可能多地存储一个随机数存储库,因为这个应用程序在 24 小时时钟上运行,支持多个地理区域(多个蒙特carlo 模拟运行),大约 70% 的一天最大 cpu 负载,全天的剩余时间我想在所有可用内存空间中缓冲随机数。存储到磁盘太慢了,有点挫败我可以缓冲到这个随机数内存缓存中的任何收益。
            【解决方案10】:

            好吧,我在大型数据集上遇到了类似的问题,并且试图强制应用程序使用这么多数据并不是真正正确的选择。我能给你的最好的建议是尽可能小块地处理你的数据。因为处理这么多数据,问题迟早会回来。此外,您无法知道将运行您的应用程序的每台机器的配置,因此始终存在异常发生在另一台电脑上的风险。

            【讨论】:

            • 其实我知道这台机器的配置,它只在一台服务器上运行,我可以为这些规格写这个。这是一个大规模的蒙特卡罗模拟,我正在尝试通过预先缓冲随机数来优化。
            【解决方案11】:

            我遇到了类似的问题,这是由于 StringBuilder.ToString();

            【讨论】:

            • 别让字符串生成器变得这么大
            【解决方案12】:

            将您的解决方案转换为 x64。如果您仍然遇到问题,请为引发异常的所有内容授予最大长度,如下所示:

             var jsSerializer = new JavaScriptSerializer();
             jsSerializer.MaxJsonLength = Int32.MaxValue;
            

            【讨论】:

              【解决方案13】:

              如果您不需要 Visual Studio 托管过程:

              取消勾选选项:Project->Properties->Debug->Enable the Visual Studio Hosting Process

              然后构建。

              如果您仍然遇到问题:

              转到项目->属性->构建事件->构建后事件命令行并粘贴以下内容:

              call "$(DevEnvDir)..\..\vc\vcvarsall.bat" x86
              "$(DevEnvDir)..\..\vc\bin\EditBin.exe" "$(TargetPath)"  /LARGEADDRESSAWARE
              

              现在,构建项目。

              【讨论】:

                【解决方案14】:

                将 Windows 进程限制增加到 3gb。 (通过 boot.ini 或 Vista 启动管理器)

                【讨论】:

                • 真的吗?默认的最大进程内存是多少?以及如何改变它?如果我在我的 PC 上玩游戏或其他东西,它可以轻松地从单个 EXE/进程中使用 2+ GB,我认为这不是问题所在。
                • /3GB 对此来说太过分了,并且会导致很多不稳定,因为许多驱动程序认为用户空间指针总是指向较低的 2GB。
                • m3ntat:不,在 32 位 Windows 中,单个进程被限制为 2GB。剩下的 2GB 地址空间由内核使用。
                猜你喜欢
                • 2010-09-27
                • 2013-09-09
                • 2013-08-09
                • 2010-10-29
                • 1970-01-01
                • 2018-02-05
                • 1970-01-01
                相关资源
                最近更新 更多