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

问题描述 投票:83回答:14

这是我的代码:

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

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

更新

我认为将其分解为更小的块并逐渐增加我的内存需求将有助于如果问题是由于内存碎片,但它不会无法超过256mb的总ArrayList大小,无论我做什么调整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;
c# memory-management out-of-memory
14个回答
129
投票

你可能想读这个:Eric Lippert的““Out Of Memory” Does Not Refer to Physical Memory”。

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

关键点:

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

0
投票

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


0
投票

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


0
投票

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

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

0
投票

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

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

然后建立。

如果你仍然面临问题:

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

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

现在,构建项目。


-2
投票

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


25
投票

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

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

24
投票

检查您是否正在构建64位进程,而不是32位进程,这是Visual Studio的默认编译模式。为此,右键单击您的项目,属性 - >构建 - >平台目标: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>

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数量的限制。

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


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


5
投票

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

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

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

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

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

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


3
投票

从32位改为64位对我来说很有用 - 如果您使用的是64位PC而且不需要移植,那值得一试。


2
投票

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

LP,Dejan


1
投票

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


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语句生成它们。你不会用这种方式耗尽内存。

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

© www.soinside.com 2019 - 2024. All rights reserved.