当仍有大量内存空闲时,抛出'System.OutOfMemoryException'

时间:2009-07-20 13:50:54

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

这是我的代码:

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上有足够的空间来处理760mb的100000000随机数。我需要在给定可用内存的情况下尽可能多地存储随机数。当我投入生产时,盒子上将有12GB,我想要使用它。

CLR是否将我限制为默认的最大内存?以及如何申请更多?

更新

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

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;

14 个答案:

答案 0 :(得分:126)

您可能需要阅读:““Out Of Memory” Does Not Refer to Physical Memory”作者Eric Lippert。

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

要点:

  • 在我看来,我们称之为“进程内存”的数据存储最好被视为磁盘上大量文件
  • RAM只能被视为性能优化
  • 您的程序消耗的虚拟内存总量与其性能实际上并不是很相关
  • “用完RAM”很少会导致“内存不足”错误。而不是错误,它会导致性能不佳,因为存储实际上在磁盘上的事实的全部成本突然变得相关。

答案 1 :(得分:25)

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

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

答案 2 :(得分:23)

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

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

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

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

答案 3 :(得分:7)

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

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);
}

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

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

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

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


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

答案 4 :(得分:5)

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

许多Windows驱动程序未使用此选项进行测试,因此其中相当一部分假设用户模式指针始终指向地址空间的较低2GB。这意味着它们可能会以/ 3GB的速度破坏。

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

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

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

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

答案 5 :(得分:3)

从32位改为64位对我来说非常有用 - 如果你使用的是64位个人计算机而且不需要移植就值得一试。

答案 6 :(得分:2)

如果你需要这么大的结构,也许你可以利用内存映射文件。 这篇文章可能有用: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

LP, 德扬

答案 7 :(得分:1)

32位窗口有2GB进程内存限制。其他人提到的/ 3GB启动选项将使这个3GB仅剩1GB用于操作系统内核。实际上,如果你想毫不费力地使用2GB以上,那么就需要64位操作系统。这也解决了这个问题,即虽然你可能有4GB的物理内存,但视频卡所需的地址空间可能会使该内存的大量内存无法使用 - 通常大约为500MB。

答案 8 :(得分: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语句生成它们。你不会用这种方式耗尽内存。

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

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

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

取消选中以下选项:Project-> Properties-> Debug->启用Visual Studio托管过程

然后构建。

如果您仍然遇到问题:

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

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

现在,构建项目。

答案 13 :(得分:-2)

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