如果内存可用,为什么会抛出内存不足异常?

10
我有一个相当简单的C#应用程序,构建了一个大的哈希表。这个哈希表的键是字符串,值是整数。
程序运行良好,直到添加了约1030万个项到哈希表时,就会在添加项到哈希表的那一行抛出内存不足错误。
根据任务管理器的显示,我的程序只使用了797mb的内存,还有超过2gb可用。这是一台32位机器,因此我知道一个进程只能使用2gb的内存,但仍然留下了大约1.2gb的空间可以扩展哈希表。
为什么会抛出内存不足错误?

1
顺便说一句,我希望你明白这与C#无关。 - John Saunders
2
为什么在运行时需要一个拥有1030万项的哈希表? - this. __curious_geek
是的,我明白这是一个 .Net 框架的问题,而不是 C# 的问题。我需要在遍历一棵大树时在哈希表中存储数百万个项,并且需要在每个节点上存储其中一个属性的分布情况。我认为使用一个大的哈希表比使用成千上万个较小的哈希表更好。 - Paul
1
有时候抛出OutOfMemoryException只是为了捉弄运维团队,它并不总是意味着没有更多的内存。 :P - Rex M
@Paul:你具体使用的是哪个.NET的“hashtable”类?是Hashtable吗?你应该尝试使用其他集合,比如Dictionary<string,int> - John Saunders
显示剩余3条评论
7个回答

11

理论上,您可以为该进程获得2GB的内存,但实际上这是2GB连续内存,因此如果您的进程内存是碎片化的,则可用内存少于2GB。

此外,我怀疑哈希表(就像大多数数据结构一样)默认情况下会在需要扩展时加倍大小,因此当添加超过其临界点的项目时会导致巨大的增长。

如果您事先知道它需要的大小(或有一个合理的超估计),在构造函数中指定容量可能会有所帮助。

另外,如果不一定非要将其存储在内存中,某种数据库解决方案可能会更好,并且如果它无法适合内存时,可以提供更多的灵活性。


1
不完全准确:每个进程都有自己的2GB连续虚拟内存。碎片化发生在进程内部,因为进程中两个连续的内存页面实际上可能与内核不连续。 - lornova
谢谢...我已经将其重新设置为1700万容量,并且我们将看看它的表现如何。数据来自数据库,但我无法在那里运行过程本身,因为递归查询非常慢。我可以将每个(项目,节点)实例插入到数据库中以供之后处理,但由于网络速度较慢,我预计这将非常缓慢。 - Paul
@Lorenzo 谢谢,已更新以反映它是进程内存碎片而不是系统。 - Davy8
@Paul 如果这个方法不起作用,并且使用当前数据库的问题是网络速度的原因,也许使用类似SQLite的轻量级本地临时数据库会更合适? - Davy8
@Davy8:+1,让我们达到10K! - ABCD

4
可能是由于内存碎片化导致的:你仍然有空闲内存,但不是连续的。内存被划分为“页面”,通常为4KB大小,因此如果你分配了4MB,你将需要在你的进程地址空间中拥有1024个连续的内存页面(它们不必在物理上连续,因为内存被每个进程虚拟化)。
然而,哈希表的内存并不一定是连续的(除非实现得非常糟糕),所以也许这是内存管理器的某些限制...

3
使用Process Explorer (www.sysinternals.com) 并查看您的进程的虚拟地址空间。与“私有字节”(进程所占用的内存量)相比,虚拟地址空间显示正在使用的最高内存地址。如果碎片化很高,则其值将远高于“私有字节”。
如果您的应用程序确实需要那么多内存:
  • 考虑转换为64位
  • 启用 /LARGEADDRESSAWARE 标志,在64位操作系统下给予32位进程4GB的RAM,在带有 /3GB 标志的32位Windows中引导则只有3GB。

1

你正在运行的程序由于Visual Studio调试器试图跟踪应用程序中的所有操作(断点、引用、堆栈等)而具有有限的资源。

除此之外,你可能还有更多的未处理的内容--垃圾回收器是分层的,并且对大对象的回收速度非常缓慢。

    +-------+
    | large |       collected less often (~1/10+ cycles)
  +-+-------+-+              |
  |   medium  |              |
+-+-----------+-+            V
|     small     |   collected more often (~1/3 cycles)
+---------------+

注意:这些数字是从记忆中得出的,因此请保持警惕。


让我澄清一下 - 运行在调试模式下,而不是发布模式。 - Tim

1

这是Windows XP...我不认为任务管理器中有“提交大小”。 - Paul
这是一个很好的回答,但现在问题变成了:为什么提交大小比实际内存使用量大得多? - lornova
"Lorenzo: "Commit size" 是应用程序保留供使用的内存量(即执行了内存分配命令) - 并不一定意味着它实际上正在被使用。"私有工作集" 是已被进程分配和使用的无法与其他进程共享的内存量。" - Warren Rumak
@Paul,好的,由于我没有XP机器来检查,我不知道它在哪个版本上可用。无论如何,问题是只有分配给应用程序的内存页面才被计算为工作集,已交换或稀疏的页面不会被计算在内。Windows似乎有时会优先考虑缓存而不是一些交换,这意味着即使您认为有空闲内存可用,您的应用程序可能仍然部分交换出去。 - Lucero

0

我通过在“生成”选项卡下的exe文件项目属性页面中取消选中“优先使用32位”复选框来解决了这个问题。


0
你应该使用类似于数组和链表之间的混合结构。因为链表每个元素占用的内存比数组更多,但是数组需要连续的内存空间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接