内存不足异常,即使似乎有足够的内存可用

out-of-memory exception even though enough memory seems to be available

有时我们的客户会在我们的应用程序中观察到内存不足的异常。由于我们记录了他们的操作,我们可以粗略地重现他们所做的事情,但是如果我这样做并使用 dotMemory 分析应用程序,我无法重现异常并且使用的内存(大约 100 MB 托管 + 500MB 非托管)远低于限制(2GB,因为它是一个 32 位应用程序)。此外,在捕获到异常时,使用 Process.GetCurrentProcess().WorkingSet64 请求当前内存使用量,这表明内存使用量在 500 到 900 MB 之间。我知道这个数字不是很可靠,但它是另一个迹象表明应该有足够的可用内存。

该应用程序的一个相关 属性 是它处理测量的时间序列(成对的 DateTime 和 double 存储在一个数组中)。这些对象可能足够大,可以存储在大型对象堆 (LOH) 中。因此,堆碎片确实发生了,但是在分析时这 而不是 似乎是一个大问题。 LOH 的大小小于 100MB 包括 漏洞。

有没有可能在抛出内存不足异常后调用垃圾收集器(GC)?我认为,在内存分配请求未得到满足的情况下,仅当 GC 未能收集到足够的内存时才会抛出异常。但是,与在第 0 代堆中分配的内存相比,在 LOH 中分配的内存可能有所不同?

有没有人知道我们如何解决这个问题?

我们正在使用 VS 2010 SP1 和 .NET 4.0。 该问题可能与 here 提出的问题有关, here and here,但我在那里没有找到满意的答案。

更新:添加了示例性堆栈跟踪和堆碎片图表

没有唯一触发内存不足异常的地方,但既然被要求了,我加个strack trace:

Exception of type 'System.OutOfMemoryException' was thrown.
mscorlib
  at System.Runtime.Serialization.ObjectIDGenerator.Rehash()
  at System.Runtime.Serialization.ObjectIDGenerator.GetId(Object obj, Boolean& firstTime)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.InternalGetId(Object obj, Boolean assignUniqueIdToValueType, Type type, Boolean& isNew)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Schedule(Object obj, Boolean assignUniqueIdToValueType, Type type, WriteObjectInfo objectInfo)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteMembers(NameInfo memberNameInfo, NameInfo memberTypeNameInfo, Object memberData, WriteObjectInfo objectInfo, NameInfo typeNameInfo, WriteObjectInfo memberObjectInfo)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteMemberSetup(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo, String memberName, Type memberType, Object memberData, WriteObjectInfo memberObjectInfo)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo, String[] memberNames, Type[] memberTypes, Object[] memberData, WriteObjectInfo[] memberObjectInfos)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
  at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
  ... <methods from our application follow>

dotMemory 的以下图表描述了使用该工具工作大约一个小时后的 LOH 碎片:

使用工具vmmap找到了问题的原因:托管堆的实际可用内存远小于2GB的限制。为与 MS Office 工具交互而加载的几个共享库 (~400 MB)。还有本机代码 dll(~300MB),它们也分配非托管堆(~300MB)。还有很多其他东西,最后,托管堆只剩下大约 700MB。

由于可用内存比我原先想象的要少得多,所以 LOH 碎片的影响可能比我怀疑的要大,事实上:vmmap 显示,该内存区域中最大的空闲块随着时间的推移变得更小,即使可用内存保持不变。我认为,这证明碎片化是问题的原因。异常的触发器通常是我们有时用于深度复制对象的二进制序列化。它似乎导致内存使用量达到峰值。

那该怎么办呢?我正在考虑以下选项:

  • 切换到 x64(这将在很长一段时间内发生 运行)
  • 切换到 .NET 4.5.1 允许 defragment LOH
  • 减少一般内存使用:如果还有大约 200MB 可用,碎片整理似乎需要更长的时间。当一些大库没有加载时会发生这种情况。在我的实验中,我无法再触发内存不足异常。
  • 更改代码,这可能会太耗时