11

我尝试向一个Collection中添加大量元素,这些元素每个简单的数据传输对象都有五个基本数据类型的属性,没什么特别的。

在循环中添加新条目时,我总是得到 OutOfMemoryException。有趣的是,在尝试添加第 8388608 个元素(即 8*1024*1024)时,我总是遇到异常。因此,我假设在此类集合中允许的容量(元素数量)方面存在内置限制,但我找不到任何有关它的信息。

这个限制真的存在吗?我在哪里可以找到这个记录?

4

4 回答 4

15

这是一个 OutOfMemoryException,所以这里讨论的不是集合的大小或容量:它是应用程序中的内存使用。诀窍是您不必用尽机器甚至进程中的内存来获取此异常。

我认为正在发生的是你正在填满大对象堆。随着收藏的增长,他们需要在后台添加存储以容纳新项目。一旦分配了新的存储并复制了项目,旧的存储就会被释放并且应该可以进行垃圾回收。

问题是,一旦超过一定大小(过去是 85000 字节,但现在可能不同),垃圾收集器 (GC) 就会使用称为大对象堆 (LOH) 的东西来跟踪您的内存。当 GC 从 LOH 中释放内存时(这种情况开始时很少发生),内存将返回到您的操作系统并可供其他进程使用,但该内存中的虚拟地址空间仍将在您自己的进程中使用. 你的程序的地址表中会有一个巨大的漏洞,因为这个漏洞在大对象堆上,它永远不会被压缩或回收。

您在 2 的精确幂中看到此异常的原因是大多数 .Net 集合使用加倍算法来向集合添加存储。它总是会在需要再次加倍的地方抛出,因为在那之前,RAM 已经被分配了。

因此,一个快速的解决方案是利用大多数 .Net 集合中很少使用的功能。如果您查看构造函数重载,大多数集合类型都会有一个允许您在初始构造期间设置容量。此容量不是硬性限制——它只是一个起点——但它在少数情况下很有用,包括当你的集合会变得非常大时。您可以将初始容量设置为淫秽的东西……希望足够大以容纳所有物品,或者至少只需要“加倍”一次或两次。

您可以通过在控制台应用程序中运行以下代码来看到这种效果:

var x = new List<int>();
for (long y = 0; y < long.MaxValue; y++)
    x.Add(0);

在我的系统上,这会在 134217728 个项目之后引发 OutOfMemory 异常。134217728 * 每个 int 4 个字节仅(并且确切地说)512MB 的 RAM。它不应该抛出,因为这是该过程中唯一具有任何实际大小的东西,但无论如何它都会因为地址空间丢失给旧版本的集合。

现在让我们更改代码以设置容量,如下所示:

var x = new List<int>(134217728 * 2);
for (long y = 0; y < long.MaxValue; y++)
    x.Add(0);

现在我的系统在抛出时一直到 268435456 个项目(1GB 的 RAM),因为它不能将 1GB 翻倍,这要归功于进程使用的其他 ram 吃掉了 2GB 虚拟地址表限制的一部分(即:循环计数器以及来自集合对象和进程本身的任何开销)。

我无法解释的是它不允许我使用 3 作为乘数,即使那只是(!)1.5GB。一个使用不同乘数的小实验试图找出我能得到多大,结果表明这个数字并不一致。在某一时刻,我能够达到 2.6 以上,但后来不得不回落到 2.4 以下。有新发现,我猜。

如果此解决方案确实为您提供了足够的空间,您还可以使用一个技巧来获得 3GB 的虚拟地址空间,或者您可以强制您的应用程序编译为 x64 而不是 x86 或 AnyCPU。如果您使用的是基于 2.0 运行时的框架版本(通过 .Net 3.5 的任何版本),您可能会尝试更新到 .Net 4.0 或更高版本,据报道这会更好一些。如果做不到这一点,您将不得不完全重写您如何处理数据,这可能涉及将其保存在磁盘上,并且一次只在内存中保存一个项目或项目的小样本(缓存)。我真的推荐最后一个选项,因为其他任何东西最终都可能会意外再次中断(如果你的数据集一开始就这么大,它也可能会增长)。

于 2012-12-19T14:49:39.220 回答
3

在此处查看 Marc 的答案List<T> 中的最大项目是什么?.

您可能确实对当前进程有一个 OutOfMemoryException。

于 2012-12-19T14:34:55.503 回答
2

OutOfMemoryException并不意味着您已经达到了集合中元素数量的硬性限制,这意味着您已经达到了当前进程中可以保存在内存中的数据量的硬性限制。

根据可用内存、当前使用的内存等,它会因机器而异。

于 2012-12-19T14:33:38.093 回答
0

限制是您可以为集合类设置的容量,很可能int.MaxValue2147483647(在您的情况下肯定是)。但是,当您用完内存时,无论您是否达到该硬限制,都会出现 OOM 异常。

于 2012-12-19T14:35:19.960 回答