102

这是我的代码:

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 是否将我限制在默认的最大内存开始?我该如何要求更多?

更新

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

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;
4

14 回答 14

149

您可能想阅读以下内容:Eric Lippert 的“ “内存不足”不指物理内存”。

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

关键点:

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

检查您构建的是 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>
于 2013-06-26T13:56:06.753 回答
26

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

  1. 您可以尝试使用 /3GB (正如其他人建议的那样)
  2. 或者切换到 64 位操作系统。
  3. 或者修改算法,使其不需要大量内存。也许分配一些较小(相对)的内存块。
于 2009-07-20T13:59:45.333 回答
10

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

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]

如果您总是按顺序访问值,另一个选择可能是使用重载的构造函数来指定种子。这样,您将获得一个半随机数(如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函数解决它)。

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

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

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


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

于 2012-12-24T23:15:42.543 回答
5

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

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

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

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

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

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

于 2009-07-20T14:05:46.637 回答
5

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

于 2012-08-31T21:04:27.410 回答
2

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

LP,德扬

于 2009-07-20T14:09:09.693 回答
1

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

于 2009-07-20T14:03:07.857 回答
1

您可以尝试使用迭代器,而不是分配一个庞大的数组吗?这些是延迟执行的,这意味着值仅在 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 语句要求时生成它们。这样你就不会耗尽内存。

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

于 2009-07-20T17:05:39.003 回答
0

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

于 2009-07-20T14:05:36.653 回答
0

我有一个类似的问题,这是由于 StringBuilder.ToString();

于 2017-03-30T10:57:24.537 回答
0

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

 var jsSerializer = new JavaScriptSerializer();
 jsSerializer.MaxJsonLength = Int32.MaxValue;
于 2017-11-13T13:58:06.370 回答
0

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

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

然后构建。

如果您仍然遇到问题:

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

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

现在,构建项目。

于 2018-11-22T04:47:27.643 回答
-2

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

于 2009-07-20T13:53:33.730 回答