Интересное OutOfMemoryException с StringBuilder

Мне нужно постоянно создавать большие строки в цикле и сохранять их в базе данных, которая в настоящее время вносит в нее OutOfMemoryException.

Что в основном происходит здесь, я создаю строку, используя XmlWriter с StringBuilder на основе некоторых данных. Затем я вызываю метод из внешней библиотеки, которая преобразует эту строку xml в некоторую другую строку. После этого преобразованная строка сохраняется в базе данных. Все это делается многократно в цикле около 100 раз для разных данных.

Строки сами по себе не слишком велики (ниже 500 кбайт каждая), и память процесса не увеличивается во время этого цикла. Но все же иногда я получаю OutOfMemeoryExcpetion внутри StringBuilder.Append. Интересно, что это исключение не приводит к сбою. Я могу поймать это исключение и продолжить цикл.

Что здесь происходит? Почему я должен получить OutOfMemoryException, хотя в системе все еще достаточно свободной памяти? Это какая-то проблема кучи GC?

Учитывая, что я не могу обойти преобразование всех этих строк, что я мог сделать, чтобы сделать эту работу надежной? Должен ли я использовать коллекцию GC? Должен положить a Thread.Sleep в цикл? Должен ли я перестать использовать StringBuilder? Следует просто повторить попытку с помощью OutOfMemoryException?

Ответ 1

Существует память, но не непрерывный сегмент, который может обрабатывать размер вашего построителя строк. Вы должны знать, что каждый раз, когда буфер строкового построителя слишком короткий, его размер удваивается. Если вы можете определить (в ctor) размер вашего строителя, то лучше. Вы можете позвонить GC.Collect(), когда закончите с большой коллекцией объектов.

На самом деле, когда у вас есть OutOfMemory, он вообще показывает плохой дизайн, вы можете использовать жесткий диск (временные файлы) вместо памяти, вам не следует выделять память снова и снова (попробуйте повторно использовать объекты/буферы/...).

Я настоятельно рекомендую вам прочитать это сообщение "Из памяти" не относится к физической памяти от Эрика Липперта.

Ответ 2

Попробуйте повторно использовать объект StringBuilder, когда вы создаете данные.

После или до использования просто reset размер 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(...);
    //
}

Ответ 3

С указанными вами размерами вы, вероятно, работаете в фрагментации Large Object Heap (LOH).

Повторное использование объектов StringBuilder не является прямым решением, вам нужно получить доступ к базовым буферам.
Если возможно, рассчитать или оценить размер заранее и предварительно выделить.

И это может помочь, если вы объедините выделение вверх, скажем, кратным 20 или около того. Это может улучшить повторное использование.