当仍有大量可用内存时,抛出“ System.OutOfMemoryException”


92

这是我的代码:

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

更新资料

我认为如果问题是由于内存碎片造成的话,将它分成较小的块并逐步增加我的内存需求将会有所帮助,但是不管我做什么调整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;

6
我建议对您的应用程序进行重新架构,以免您使用太多内存。您正在做什么,一次需要将一亿个数字全部存储在内存中?
埃里克·利珀特

2
您尚未禁用页面文件或类似的愚蠢的东西,对吗?
jalf

@EricLippert,我在解决P vs. NP问题(claymath.org/millenium-problems/p-vs-np-problem)时遇到了这个问题。您是否有减少工作内存使用量的建议?(例如,将数据块序列化并存储在硬盘上,使用C ++数据类型等)
devinbost 2014年

@bosit这是一个问答网站。如果您对实际代码有特定的技术问题,请将其发布为问题。
埃里克·利珀特

@bostIT您评论中的P与NP问题链接不再有效。
RBT

Answers:


140

您可能需要阅读以下内容:埃里克·利珀特(Eric Lippert)的“ “内存不足”不涉及物理内存 ”。

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

关键点:

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

“如果您有100个块,每个块有4 MB,那么当您需要一个5 MB块时,这将无济于事” -我认为最好作一个小小的修正:“如果您有100个“孔”块”
OfirD

31

检查您正在构建的是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>

你救了我。谢谢!
Tee Zad Awk

25

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

  1. 您可以尝试使用/ 3GB(如其他人所建议)
  2. 或切换到64位操作系统。
  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]

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

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

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

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


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


5

我建议不要使用/ 3GB Windows启动选项。除了其他所有功能(对于一个行为不佳的应用程序执行此操作是过大的,反正它可能也无法解决您的问题),它可能会导致很多不稳定因素。

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

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

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

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

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


将工作拆分为较小的块似乎也无济于事。
m3ntat

4

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



1

32位Windows的进程内存限制为2GB。其他人提到的/ 3GB引导选项将使该3GB剩下的1GB可供OS内核使用。实际上,如果您要使用2GB以上的内存而不麻烦,则需要64位操作系统。这也解决了一个问题,尽管您可能有4GB的物理RAM,但视频卡所需的地址空间会使该内存的大量存储无法使用-通常约为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语句的要求生成它们。这样您将不会用完内存。

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


迭代方法,但是我需要在其余应用程序的任何空闲时间中尽可能多地存储一个随机数存储库,因为此应用程序在24小时制下运行,支持多个地理区域(多次蒙特卡洛模拟运行),大约70%最大一天的CPU负载,我想在一整天的剩余时间中缓冲所有可用内存空间中的随机数。存储到磁盘的速度太慢,并且无法击败我可以缓存到此随机数内存缓存中的任何收益。
m3ntat

0

好吧,我在大型数据集上遇到了类似的问题,试图迫使应用程序使用大量数据并不是正确的选择。我能给你的最好的建议是,如果可能的话,以小块方式处理数据。因为要处理大量数据,所以该问题迟早会再次出现。另外,您不知道将运行您的应用程序的每台计算机的配置,因此始终存在在另一台PC上发生异常的风险。


实际上,我知道机器的配置,该配置仅在一台服务器上运行,我可以为那些规格编写此配置。这是用于大规模的蒙特卡洛模拟,我正尝试通过预先缓冲随机数来进行优化。
m3ntat


0

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

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

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

现在,构建项目。


-2

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


真?默认的最大进程内存是多少?以及如何改变呢?如果我在PC上玩游戏或玩某些游戏,则可以很容易地在一个EXE /进程上使用2 GB以上的内存,我认为这不是问题所在。
m3ntat

/ 3GB对此是过大的,并且会导致很多不稳定,因为许多驱动程序都假定用户空间指针始终指向较低的2GB。
jalf

1
m3ntat:否,在32位Windows中,单个进程限制为2GB。剩余的2GB地址空间由内核使用。
jalf

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.