【问题标题】:Why Would an Out of Memory Exception be Thrown if Memory is Available?如果内存可用,为什么会抛出内存不足异常?
【发布时间】:2011-03-08 20:21:13
【问题描述】:

我有一个相当简单的 C# 应用程序,它构建了一个大型哈希表。这个哈希表的键是字符串,值是整数。

程序运行良好,直到大约 1030 万个项目被添加到哈希表中,这时在向哈希表添加项目的行上抛出内存不足错误。

根据任务管理器,我的程序只使用了 797mb 的内存,还有超过 2gb 可用。它是一台 32 位机器,所以我知道一个进程总共只能使用 2gb,但仍然留下了大约 1.2gb 的哈希表应该能够扩展到的空间。

为什么会抛出内存不足错误?

【问题讨论】:

  • 顺便说一句,我希望你明白这与 C# 无关?
  • @John:但也许/可能它与 .net 相关。
  • @Lorenzo:这就是我的观点。不是 C#,而是 .NET
  • 为什么在运行时需要 1030 万个哈希表中的项目?
  • 这并不总是意味着没有更多的内存。有时我让我的应用程序抛出 OutOfMemoryException 只是为了扰乱运营团队:P

标签: c# .net memory


【解决方案1】:

理论上,您可以获得 2GB 的进程,但实际情况是它是 2GB 的连续内存,因此如果您的进程的内存是碎片化的,那么您获得的内存会更少。

此外,我怀疑哈希表与大多数数据结构一样,默认情况下会在需要增长时翻倍,从而在添加临界点项目时导致巨大的增长。

如果您提前知道它需要的大小(或有合理的高估),那么在构造函数中指定容量可能会有所帮助。

或者,如果它在内存中并不重要,那么某种数据库解决方案可能会更好,并且如果它确实达到了无法放入内存的程度,则可以为您提供更大的灵活性。

【讨论】:

  • 不完全是:每个进程都有他的私有 2GB 连续虚拟内存。碎片在进程内部,因为进程中的两个连续内存页实际上可能与内核不连续。
  • 谢谢...我已经将它设置为再次运行,容量为 1700 万,我们将看看它是如何运行的。数据来自数据库,但我不能在那里运行程序本身,因为递归查询非常慢。我可以将 (item, node) 的每个实例插入到数据库中以供其处理,但我预计由于网络速度,它会非常慢
  • @Lorenzo 谢谢,更新以反映它是进程内的内存碎片而不是系统。
  • @Paul 如果这不起作用并且网络速度是使用当前数据库的问题,也许使用 SQLite 之类的轻量级本地临时数据库可能是合适的?
  • @Davy8 : +1 达到你 10K!
【解决方案2】:

可能是由于内存碎片:您仍有可用内存但不连续。内存被划分为pages,通常大小为 4KB,因此如果分配 4 MB,则在进程寻址空间中将需要 1024 个连续的内存页面(它们还没有em>物理上是连续的,因为内存是按进程虚拟化的)。

但是哈希表的内存并不是连续的(除非它的实现非常糟糕),所以可能是内存管理器的一些限制......

【讨论】:

    【解决方案3】:

    使用 Process Explorer (www.sysinternals.com) 并查看您的进程的虚拟地址空间。与“Private Bytes”(进程占用的内存量)相比,虚拟地址空间显示了正在使用的最高内存地址。如果碎片很高,它将远高于“Private Bytes”。

    如果你的应用真的需要这么多内存:

    • 考虑转为 64 位
    • 启用 /LARGEADDRESSAWARE 标志,这将为您的 32 位进程在 64 位操作系统下提供 4GB RAM,如果使用 /3GB 标志启动 32 位 Windows,则为 3GB。

    【讨论】:

      【解决方案4】:

      您只是在查看错误的列。看看“提交大小”列,这个应该是 2GB 左右。

      http://windows.microsoft.com/en-us/windows-vista/What-do-the-Task-Manager-memory-columns-mean

      【讨论】:

      • 这是 Windows XP...我认为它在任务管理器中没有“提交大小”
      • 这是一个很好的答案,但现在问题变成了:为什么提交大小比实际内存使用量大?
      • Lorenzo:“提交大小”是保留给应用程序使用的内存量(即执行了内存分配命令)——并不一定意味着它实际上正在被使用。 “私有工作集”是进程已分配/和/使用的内存量,不能与其他进程共享。
      • @Paul,好的,因为我没有可以检查的 XP 机器,所以我不知道它适用于哪个版本。无论如何,问题是只有分配给应用程序的内存页面才算作工作集,换出或稀疏的页面不会被计入。Windows 有时似乎更喜欢缓存而不是某些交换,这意味着您的应用程序可能即使您认为有可用的可用内存,也会被部分换出。
      【解决方案5】:

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

      除此之外,你可能还有比你想象的更多的东西——垃圾收集器是分层的,收集大对象的速度非常慢。

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

      注意:这些数字来自记忆,因此请谨慎对待。

      【讨论】:

      • 让我澄清一下——在调试模式下运行,而不是在发布模式下运行。
      【解决方案6】:

      你应该在数组和链表之间使用 hibrid 之类的东西。因为链表每项占用的内存比数组多,但数组需要连续的内存空间。

      【讨论】:

        【解决方案7】:

        我通过取消选中“构建”选项卡下 exe 的“项目属性”页面中的“首选 32 位”复选框解决了我的这个问题

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-14
          • 1970-01-01
          • 2011-09-19
          • 1970-01-01
          • 2015-07-11
          相关资源
          最近更新 更多