生成报告时诊断.NET OutOfMemoryException

con*_*tor 7 .net memory

我的任务是改进一段代码,以任何我认为合适的方式生成大量报告.

生成了大约10个相同的报告(对于数据库的每个"部分"),它们的代码与此类似:

GeneratePurchaseReport(Country.France, ProductType.Chair);
GC.Collect();
GeneratePurchaseReport(Country.France, ProductType.Table);
GC.Collect();
GeneratePurchaseReport(Country.Italy, ProductType.Chair);
GC.Collect();
GeneratePurchaseReport(Country.Italy, ProductType.Table);
GC.Collect();
Run Code Online (Sandbox Code Playgroud)

如果我删除这些GC.Collect()呼叫,报告服务将崩溃OutOfMemoryException.

大部分内存保存在一个List<T>内部,GeneratePurchaseReport并且一旦退出就不再使用 - 这就是为什么完整的GC集合将回收内存.

我的问题是双重的:

  1. 为什么GC不能单独执行此操作?一旦它在第二个内存耗尽,GeneratePurchaseReport它应该在崩溃和刻录之前进行完整的收集,不应该吗?
  2. 是否存在内存限制,我可以以某种方式提出?我根本不介意将数据交换到磁盘,但.net进程使用的内存远远少于可用的2.5GB内存!我希望它只会在地址空间耗尽时崩溃,但在64位机器上我怀疑这种情况发生得如此之快.

Joe*_*orn 5

阅读大型物体堆.

我认为正在发生的事情是,个别报告的最终文档是随着时间的推移而构建和附加的,这样在每次追加操作时都会创建一个新文档并丢弃旧文档(这可能发生在幕后).该文档(最终)大于85,000字节的大对象堆存储阈值.

在这种情况下,您实际上并没有使用那么多物理内存 - 它仍可用于其他进程.您使用的是您的程序可用的地址空间.Windows中的每个进程都有自己的(通常)2GB可用地址空间.随着时间的推移,您分配了不断增长的报告文档的新副本,在收集前一个副本时,您会在LOH中留下许多漏洞.由先前对象释放的内存实际上不再使用,并且可用于其他进程,但地址空间仍然丢失; 它是分散的,需要压缩.最终这个地址空间填满,你得到一个OutOfMemory异常.

证据表明,对GC.Collect()的调用允许对LOH进行一些压缩,但这不是一个完美的解决方案.几乎所有我在这个主题上读过的内容都表明GC.Collect()根本不应该压缩LOH,但我已经看到了几个轶事报道(一些在Stack Overflow上),在那里调用GC.Collect()实际上能够避免LOH碎片中的OutOfMemory异常.

一个"更好"的解决方案(确保你永远不会耗尽内存 - 使用GC.Collect()压缩LOH只是不可靠)是将你的报告分成小于85000字节的单位,并将它们全部写入一个缓冲区中,或者使用一种数据结构,这种数据结构不会随着它的增长而丢弃您以前的工作.不幸的是,这可能是更多的代码.

这里一个相对简单的选择是为MemoryStream对象分配一个比最大报告大的缓冲区,然后在构建报告时写入MemoryStream.这样你永远不会留下碎片.如果这只是写入磁盘,您甚至可以直接进入FileStream(可能通过TextWriter,以便以后更改).这个选项解决了你的问题,我想在评论这个答案时听到它.


Mit*_*eat 3

我们需要查看您的代码才能确定。

如果失败:

  • 您是否使用预期的项目数量预先确定了列表的大小?

  • 您可以预先分配并使用数组而不是列表吗?(装箱/拆箱可能会产生额外费用)

  • 即使在 64 位机器上,单个 CLR 对象的最大大小也是 2GB

  • 预先分配一个内存流来保存整个报告,然后写入该报告。

出于兴趣?:

我建议使用内存分析器,例如 memprofiler 或 Redgate(两者都有免费试用版)来查看问题的实际所在)。