尽管有足够的内存可用,仍出现内存不足异常。

3
偶尔我们的客户在应用程序中遇到了内存不足异常。由于我们记录他们的操作,因此我们可以大致重现他们所做的事情,但是如果我这样做并使用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。 该问题可能与此处提出的问题相关hereherehere,但我没有找到令人满意的答案。
更新:添加了示例堆栈跟踪和堆碎片图表
没有唯一的触发内存不足异常的地方,但由于有人要求,我添加了一个堆栈跟踪:
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碎片情况:

enter image description here


@ThomasAndreèLian - 你将他们链接到了一个在应用服务包1之前出现的.NET 1.1问题。由于他们的问题说明他们正在使用.NET 4.0,并且任何早于3.5的.NET都已经不再得到支持,你认为这可能吗? - Damien_The_Unbeliever
@Damien_The_Unbeliever,你说得完全正确,我今天可能有点慢,只看了症状就匹配了。 - Thomas Andreè Wang
@Damien_The_Unbeliever - 异常并不是从任何System.Drawing类中抛出的。它们发生在序列化期间,或者在请求大量内存的其他地方发生。 - MarkusParker
你应该先获取崩溃转储。 - Oguz Ozgul
1个回答

3
使用vmmap工具,我找到了问题的原因:可供托管堆使用的实际内存远少于2GB的限制。有几个共享库用于与MS Office工具交互(约400MB)。还有本机代码dll(约300MB),它们也分配未管理的堆(约300MB)。还有很多其他东西,在最后,只剩下约700MB用于托管堆。
由于可用内存比我最初想象的要少得多,LOH碎片可能会产生比我预想的更大的影响,事实上:vmmap显示,该内存区域中最大的空闲块随着时间的推移变得越来越小,尽管可用内存保持不变。我认为,这证明了碎片是问题的原因。异常的触发器通常是二进制序列化,我们有时用它来深度复制对象。它似乎会导致内存使用量的峰值。
那么该怎么办呢?我正在考虑以下选项:
  • 转换到x64(这终将发生)
  • 转换到.NET 4.5.1,它允许defragment LOH
  • 减少一般内存使用:如果额外有大约200MB可用,则看起来碎片整理需要更长时间。当未加载某些大型库时会出现这种情况。在我的实验中,我无法再触发内存不足异常。
  • 更改代码,但这可能会耗费太多时间

你能使用内存池来消除碎片化吗? - Jon
我猜是的,但这是否意味着重新实现 C# 的内存管理的主要部分?我担心这需要太多的努力。 - MarkusParker

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