【问题标题】:C# : Out of Memory exceptionC#:内存不足异常
【发布时间】:2012-01-23 17:41:06
【问题描述】:

今天我的应用程序抛出了OutOfMemoryException。对我来说,这几乎总是不可能的,因为我也有 4GB RAM 和大量虚拟内存。当我尝试将现有集合添加到新列表时发生错误。

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);  

据我了解,这里分配的内存不多,因为我的新列表应该包含的车辆已经存在于内存中。我不得不承认Vehicle 是一个非常复杂的类,我试图一次将大约 50.000 个项目添加到新列表中。但是由于应用程序中的所有Vehicles 都来自一个只有 200MB 大小的数据库,所以我现在不知道是什么导致了OutOfMemoryException

【问题讨论】:

  • selectedVehicles 的值(和类型)是什么?
  • OutOfMemoryException 被抛出时,您是否使用调试器附加到进程并查看问题可能是什么?对象有多大? .NET Framework 的对象大小硬限制为 2 GB,减去框架本身消耗的开销。
  • Vehicle 可能是一个结构体而不是一个类吗?
  • 200MB 的数据库空间在转换为 .net 对象时可能很容易占用两倍以上的空间。之后,它的占用空间可能会更小,但框架会尝试一次获取一大块不可用的连续内存。
  • 您关于您拥有多少内存以及您允许系统拥有多少虚拟内存的声明表明缺乏对虚拟内存和物理内存如何工作的理解。您可能想阅读有关该主题的一些信息。

标签: c# .net out-of-memory


【解决方案1】:

3 年前的话题,但我找到了另一个可行的解决方案。 如果您确定有足够的可用内存,运行 64 位操作系统并且仍然出现异常,请转到 Project properties -> Build 选项卡并确保将 x64 设置为 Platform target

【讨论】:

  • 看不到图片,有什么办法?
  • Project properties -> Build 选项卡 -> 将 Platform target 更改为 x64
【解决方案2】:

两点:

  1. 如果您运行的是 32 位 Windows,则无法访问所有 4GB,只有 2GB。
  2. 不要忘记List 的底层实现是一个数组。如果您的内存严重碎片化,则可能没有足够的连续空间来分配您的List,即使您总共有足够的可用内存。

【讨论】:

  • 在 64 位 Windows 中也只有 2 GB 可用空间。这是 .NET Framework 的限制,而不仅仅是 32 位地址空间的限制。
  • @CodyGray 每个对象(数组)将是 2GB,而不是总共 2GB。
  • @CodyGray,我认为 2GB 数组限制不在新的 .NET 版本中。
  • 可以使用gcAllowVeryLargeObjects 配置设置来克服它。更多细节在这里 - msdn.microsoft.com/en-us/library/…
【解决方案3】:

.Net4.5 不再有 2GB 的对象限制。 将此行添加到 App.config

<runtime>
    <gcAllowVeryLargeObjects enabled="true" />    
</runtime>

并且可以创建非常大的对象而不会出现 OutOfMemoryException

请注意,它仅适用于 x64 操作系统!

【讨论】:

  • 做得很好。这对我有用,注意必须将 Build 目标更改为 x64。
  • 也适用于 ASP.NET 4.5 吗?使用本地报告,My App v1 (asp.net 3.5 - clr 2.0 - classic) 工作 OK,但我的 App v2 (asp.net 4.5, clr 4.0, classic) 生成OutOfMemoryException错误,在同一个IIS服务器
  • 虽然设置了这个,但当内存达到 4.6G 时出现异常
【解决方案4】:

与应用程序中的内存相比,存储在数据库中的数据非常不同。

没有办法获得对象的确切大小,但您可以这样做:

GC.GetTotalMemory() 

在加载了一定数量的对象后,看看你的内存在加载列表时发生了多少变化。

如果是列表导致了过多的内存使用,那么我们可以寻找将其最小化的方法。例如,为什么您首先要一次将 50,000 个对象全部加载到内存中。根据需要调用数据库不是最好吗?

如果您看一下这里:http://www.dotnetperls.com/array-memory,您还会看到 .NET 中的对象大于它们的实际数据。通用列表比数组更占内存。如果你的对象中有一个通用列表,那么它会增长得更快。

【讨论】:

    【解决方案5】:

    OutOfMemoryException(在 32 位机器上)与内存的实际硬限制一样经常与碎片有关 - 您会发现很多关于此的内容,但这是我在谷歌上的第一个简短讨论:http://blogs.msdn.com/b/joshwil/archive/2005/08/10/450202.aspx。 (@Anthony Pegram 在上面的评论中提到了同样的问题)。

    也就是说,上面的代码还有另一种可能性:当您对列表使用“IEnumerable”构造函数时,您可能不会给对象任何提示,因为到您传递给 List 构造函数的集合的大小。如果您传递的对象不是一个集合(没有实现ICollection 接口),那么在幕后 List 实现将需要增长几次(或多次),每次都留下一个太- 需要进行垃圾收集的小数组。垃圾收集器可能无法足够快地到达那些丢弃的数组,并且您会得到错误。

    对此最简单的解决方法是使用List(int capacity) 构造函数告诉框架要分配的后备数组大小(例如,即使您正在估计并只是猜测“50000”),然后使用@987654324 @ 方法来实际填充您的列表。

    所以,如果我是对的,最简单的“修复”:替换

    List<Vehicle> vList = new List<Vehicle>(selectedVehicles);
    

    List<Vehicle> vList = new List<Vehicle>(50000);  
    vList.AddRange(selectedVehicles);
    

    所有其他 cmets 和答案仍然适用于整体设计决策 - 但这可能是一个快速修复。

    注意(正如@Alex 在下面评论的那样),这只有在 selectedVehicles 不是 ICollection 时才会出现问题。

    【讨论】:

    • 如果 selecyedVehicles 是一个集合,构造函数将分配正确的数组大小。无需通过 AddRange。
    【解决方案6】:

    我的开发团队解决了这种情况:

    我们将以下 Post-Build 脚本添加到 .exe 项目中并再次编译,将目标设置为 x86 并增加 1.5 gb,并且 x64 平台目标使用 3.2 gb 增加内存。我们的应用是 32 位的。

    相关网址:

    脚本:

    if exist "$(DevEnvDir)..\tools\vsvars32.bat" (
        call "$(DevEnvDir)..\tools\vsvars32.bat"
        editbin /largeaddressaware "$(TargetPath)"
    )
    

    【讨论】:

      【解决方案7】:

      您不应该尝试一次将所有列表带入,数据库中元素的大小与它放入内存中的元素大小不同。 如果你想处理元素,你应该使用 for each 循环并利用实体框架延迟加载,这样你就不会一次将所有元素都放入内存。 如果您想显示列表,请使用分页(.Skip() 和 .take())

      【讨论】:

        【解决方案8】:

        我知道这是一个老问题,但由于没有一个答案提到大对象堆,这可​​能对发现这个问题的其他人有用......

        .NET 中超过 85,000 字节的任何内存分配都来自大对象堆 (LOH),而不是普通的小对象堆。为什么这很重要?因为大对象堆没有被压缩。这意味着大型对象堆会变得碎片化,根据我的经验,这不可避免地会导致内存不足错误。

        在原始问题中,列表中有 50,000 个项目。在内部,列表使用数组,并假设 32 位需要 50,000 x 4bytes = 200,000 字节(如果是 64 位,则为两倍)。所以内存分配来自大对象堆。

        那么你能做些什么呢?

        如果您使用的是 4.5.1 之前的 .net 版本,那么您所能做的就是意识到问题并尽量避免它。因此,在这种情况下,如果没有一个列表包含超过 18,000 个元素,那么您可以拥有一个车辆列表,而不是车辆列表。这可能会导致一些丑陋的代码,但它是可行的解决方法。

        如果您使用的是 .net 4.5.1 或更高版本,那么垃圾收集器的行为会发生微妙的变化。如果您在即将进行大内存分配的位置添加以下行:

        System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
        

        它将强制垃圾收集器压缩大对象堆 - 仅限下一次。

        这可能不是最好的解决方案,但以下对我有用:

        int tries = 0;
        while (tries++ < 2)
        {
          try 
          {
            . . some large allocation . .
            return;
          }
          catch (System.OutOfMemoryException)
          {
            System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
            GC.Collect();
          }
        }
        

        当然,这只有在您有可用的物理(或虚拟)内存时才有用。

        【讨论】:

          【解决方案9】:

          随着 .Net 的发展,他们添加新的 32 位配置的能力似乎也在增强,这似乎让每个人都感到困惑。

          如果您使用的是 .Net Framework 4.7.2,请执行以下操作:

          转到项目属性

          构建

          取消选中“首选 32 位”

          干杯!

          【讨论】:

          • 确实如此,更改此标志会产生什么影响并不直观。但是,“首选 32 位”标志在 .Net 4.7.2 之前就存在了。
          【解决方案10】:

          虽然 GC 压缩小对象堆作为消除内存漏洞的优化策略的一部分,但出于性能原因,GC 从不压缩大对象堆**(压缩成本对于大对象(大于 85KB)来说太高了在尺寸方面))**。因此,如果您在 x86 系统中运行使用许多大型对象的程序,您可能会遇到 OutOfMemory 异常。如果您在 x64 系统中运行该程序,您可能会有一个碎片堆。

          【讨论】:

            猜你喜欢
            • 2016-07-27
            • 2010-09-17
            • 1970-01-01
            • 2012-07-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-10-05
            相关资源
            最近更新 更多