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

m3n*_*tat 86 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];
Run Code Online (Sandbox Code Playgroud)

例外:抛出了类型'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()));                
  }
}
Run Code Online (Sandbox Code Playgroud)

从我的主要方法:

int blockSize = 1000000;

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

Fre*_*örk 130

您可能需要阅读此内容:" 内存不足"并未引用物理内存 "作者:Eric Lippert.

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

关键点:

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


小智 26

检查您是否正在构建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>
Run Code Online (Sandbox Code Playgroud)


Sha*_*men 25

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

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


Tri*_*ped 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];
}
Run Code Online (Sandbox Code Playgroud)

然后,获取您将使用的特定索引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);
}
Run Code Online (Sandbox Code Playgroud)

值得注意的是,尽管在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个字节的打开块.如果你需要创建一个0.2GB的对象,你将无法做到这一点,因为没有足够大的块来容纳它,并且无法获得额外的空间(假设是32位环境).此问题的可能解决方案包括使用较小的对象,减少存储在内存中的数据量,或使用内存管理算法来限制/防止内存碎片.应该注意的是,除非您正在开发使用大量内存的大型程序,否则这不会成为问题.此外,这个问题可能出现在64位系统上,因为窗口主要受页面文件大小和系统上RAM数量的限制.

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


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


jal*_*alf 5

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

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

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

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

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

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


chr*_*ris 5

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