如何最大化大对象堆中最大的连续内存块

5

现在的情况是我正在调用远程服务器的WCF,它返回一个XML文档作为字符串。

大多数情况下,这个返回值只有几K,有时是几十K,非常偶尔会达到几百K,但很少会超过几兆字节(首要问题是我无法知道实际大小)。

正是这些罕见的情况导致了问题。我得到了一个堆栈跟踪,它开始于:

System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
   at System.Xml.BufferBuilder.AddBuffer()
   at System.Xml.BufferBuilder.AppendHelper(Char* pSource, Int32 count)
   at System.Xml.BufferBuilder.Append(Char[] value, Int32 start, Int32 count)
   at System.Xml.XmlTextReaderImpl.ParseText()
   at System.Xml.XmlTextReaderImpl.ParseElementContent()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XmlTextReader.Read()
   at System.Xml.XmlReader.ReadElementString()
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderMDRQuery.Read2_getMarketDataResponse()
   at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer2.Deserialize(XmlSerializationReader reader)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)

我看了一些资料,发现问题是由于大对象堆(Large Object Heap)过于碎片化,因此即使在调用之前快速检查StringBuilder.EnsureCapacity也只会导致OutOfMemoryException更早地抛出(而且因为我猜测所需的容量,可能实际上并不需要那么多,所以我的检查反而会带来更多问题)。一些观点认为,这个问题并没有太多解决办法。
我问自己的一些问题:
- 使用更少的内存——你检查过泄漏吗? 是的。内存使用量上下波动,但没有基本增长可以保证发生这种情况。有时它失败了,而在那个阶段之前它是成功的。 - 传输更小的数据量 不可行,这是一个第三方网络服务,我无法控制(或者至少需要很长时间才能解决,在此期间我仍然有问题)。 - 你能做些什么来减少LOH失败的可能性吗? ……现在这是最有成效的方法。这是一个32位进程(由于各种政治、技术和乏味的原因),但通常有数百兆的空闲内存(是最大失败量的多倍)。 - 我们能监视LOH吗? 使用perfmon可以跟踪堆的大小,但我不认为有一种方法可以监视可用的最大连续内存块。
问题是:有没有什么建议或尝试的事情?
4个回答

5
您可以查看绑定的TransferMode属性,查看是否满足将其从默认值“Buffered”更改为“Streamed”或“StreamedResponse”的要求。
此外,请查看maxBufferPoolSizemaxBufferSize的值。增加所使用的内部缓冲区大小可帮助处理大消息时减少内存利用率。
如果您正在接收大型消息,则maxReceivedMessageSize可能已经设置,但还是建议您查看该值。
我曾经遇到过上述某个值超过阈值而失败并出现晦涩的内存相关错误的情况。实际上,原始异常被上层应用程序隐藏了。启用WCF跟踪有助于诊断问题并查看真正的错误——我需要增加上述一个或多个绑定属性的值。
从您的帖子中我没有感受到您正在使用哪种绑定方式,但我相信这些设置在主流绑定方式中都很常见。例如,在MSDN文档basicHttpBinding中了解更多信息。
如果确实存在LOH碎片化,一旦调优措施耗尽,就无法再做什么了。可能需要滚动应用程序的回收来缓解它(我不建议这样做),但如果您已经尽了其他努力,那么可能只能这样做。

更改绑定是个好主意 - 我会试一下。滚动回收是我们的最后手段... - Unsliced
我们很幸运,通过代码改进(我们拥有两个端点),或绑定更改来缓解大型文档处理的任何问题,至少可以让我们通过定期计划的维护循环(由于修补程序、功能发布等)。@Steven 使用 windbg 进行堆分析的想法也可以带来好处。请尝试访问 http://blogs.msdn.com/tess/,你会从 Tess Ferrandez 那里找到如何入门的好信息。优秀的实验室!祝好运! - Zach Bonham

2

我无法解决任何WCF相关的问题,但如果您需要在32位进程中最大化LOH空间,您应该使应用程序支持大地址并在64位上运行它。一个支持大地址的32位进程将能够在64位Windows上寻址整个4 GB地址空间。这将为您提供一个相当大的内存块,超过了通常由该进程使用的地址空间。


1

我认为你的问题可能是使用XmlSerializer时未使用以下MSDN文章中指定的两个构造函数之一导致的汇编泄漏:

为了提高性能,XML序列化基础结构会动态生成用于序列化和反序列化指定类型的程序集。基础结构会查找并重复使用这些程序集。仅在使用以下构造函数时才会出现此行为:

XmlSerializer.XmlSerializer(Type)

XmlSerializer.XmlSerializer(Type, String)

如果您使用任何其他构造函数,则会生成多个版本的相同程序集,并且永远不会卸载,这会导致内存泄漏和性能下降。

很好,答案是缓存您的XmlSerializer(假设您甚至创建它)。

要真正弄清楚问题,您需要按照Tess告诉您的做法。她是一个天才。


1
如果可能的话,我会采用基于流的方法,并结合使用仅向前的Xml解析器,这样应该可以提高性能。
如果您不一定要使用WCF,可以编写自己的HttpRequest,然后将响应传递给XmlDeserializer,再像那样解析响应。这可能会让您更好地控制和了解问题实际发生的位置。您还可以尝试使用返回所需类型的非常大的文档的模拟服务进行实验。我们也遇到了很多LOH碎片问题,所以我真的感受到了您的痛苦。
在构建缓冲区时我注意到的一个问题是,.NET倾向于每次填满缓冲区时将容量加倍,这会导致内存碎片化,因为对于大小为10mb的文档,需要分步分配内存。如果您事先知道所需的缓冲区大小,则一次性分配它更有效率。因此,如果您知道即将到来的文档有多大,可以创建一个具有完全相同大小的StringBuilder。

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