有趣的OutOfMemoryException与StringBuilder

我需要在循环中不断构建大字符串并将它们保存到数据库中,目前偶尔会产生OutOfMemoryException

这里基本上是基于一些数据使用XmlWriterStringBuilder创建一个字符串。 然后我从外部库调用一个方法,将该xml字符串转换为其他字符串。 之后,转换后的字符串将保存到数据库中。 对于不同的数据,这整个过程在一个循环中重复完成约100次。

字符串本身不是太大(每个低于500kBy)并且在此循环期间进程内存不会增加。 但是,偶尔我会在StringBuilder.Append得到一个OutOfMemeoryExcpetion 。 有趣的是,此exception不会导致崩溃。 我可以捕获该exception并继续循环。

这里发生了什么? 尽管系统中仍有足够的可用内存,为什么我会得到一个OutOfMemoryException ? 这是一些GC堆问题吗?

鉴于我无法绕过转换所有这些字符串,我能做些什么来使这项工作可靠? 我应该强制GC收集吗? 应该将Thread.Sleep放入循环中吗? 我应该停止使用StringBuilder吗? 应该只是在面对OutOfMemoryException时重试?

有内存但没有连续的段可以处理字符串生成器的大小。 您必须知道,每次字符串生成器的缓冲区太短时,其大小都会加倍。 如果您可以定义(在ctor中)构建器的大小,那就更好了。 完成大量对象后,可以调用GC.Collect()

实际上,当你有一个OutOfMemory,它通常显示一个糟糕的设计,你可能使用硬盘驱动器(临时文件)而不是内存,你不应该一次又一次地分配内存(尝试重用对象/缓冲区/ …) 。

我强烈建议你阅读这篇文章“Out of Memory”并不是指 Eric Lippert的物理内存 。

在进行数据生成时尝试重用StringBuilder对象。

在使用之后或之前,只需将StringBuilder的大小重置为0并开始追加。 这将减少分配数量,并可能使OutOfMemory情况非常罕见。

为了说明我的观点:

 void MainProgram() { StringBuilder builder = new StringBuilder(2 * 1024); //2 Kb PerformOperation(builder); PerformOperation(builder); PerformOperation(builder); PerformOperation(builder); } void PerformOperation(StringBuilder builder) { builder.Length = 0; // // do the work here builder.Append(...); // } 

根据您提到的尺寸,您可能会遇到大对象堆 (LOH)碎片。

重用StringBuilder对象不是直接的解决方案,您需要掌握底层缓冲区。
如果可能,请事先计算或估算大小并预先分配。

如果你整理分配可能会有所帮助,让我们说20k左右的倍数。 这可以改善重用。