13

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

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

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

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

鉴于我无法规避转换所有这些字符串,我该怎么做才能使这项工作可靠?我应该强制进行 GC 收集吗?应该把 aThread.Sleep放入循环吗?我应该停止使用StringBuilder吗?遇到OutOfMemoryException?

4

3 回答 3

16

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

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

我强烈建议您阅读 Eric Lippert 的这篇文章“内存不足”不涉及物理内存

于 2009-11-20T09:57:45.490 回答
3

在生成数据时尝试重用 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(...);
    //
}
于 2009-11-20T10:12:38.520 回答
3

使用您提到的大小,您可能会遇到大对象堆(LOH) 碎片。

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

如果您将分配四舍五入,例如 20k 左右的倍数,它可能会有所帮助。这可以提高重用性。

于 2009-11-20T10:49:07.317 回答