OutOfMemory, но нет gcroots для многих объектов

Мы разрабатываем довольно большое приложение Windows Forms. На компьютерах нескольких клиентов он часто выходит из строя с исключением OutOfMemory. После получения полного дампа памяти в моменты приложения после исключения (clrdump, вызванный из обработчика UnhandledException) я проанализировал его с помощью ".NET Memory Profiler" и windbg.

Профайлер памяти показал только 130 МБ в экземплярах живых объектов. Интересно, что для многих типов объектов показано очень большое количество недостижимых экземпляров (например, 22000 недостижимых экземпляров Byte []). В статистике собственной памяти он составляет 127 МБ во всех кучах для данных (это нормально), но указывает на недостижимое 133 МБ в кучке # 2 и 640 МБ в большой куче (не нормально!).

При анализе дампа с windbg, указанные выше показатели подтверждены:

!dumpheap -stat
..... acceptable object sizes...
79330a00   467216     30638712 System.String
0016d488     4804    221756612      Free
79333470    27089    574278304 System.Byte[]

В приложении используется большое количество коротких буферов, но не протекает. Тестирование многих экземпляров Byte [] с помощью! Gcroot заканчивается без корней. Очевидно, что большинство из этих массивов недоступны, как указано профилировщиком памяти.

Просто, чтобы убедиться, что все в порядке,! finalizequeue показывает, что объекты не ждут завершения.

generation 0 has 138 finalizable objects (18bd1938->18bd1b60)
generation 1 has 182 finalizable objects (18bd1660->18bd1938)
generation 2 has 75372 finalizable objects (18b87cb0->18bd1660)
Ready for finalization 0 objects (18bd1b60->18bd1b60)

И также проверьте, что трассировка в потоке финализатора в корневой последовательности показывает, что он не заблокирован.

На данный момент я не могу диагностировать, почему GC не собирает данные (и я считаю, что это было бы интересно, так как в процессе закончилась память.)

edit: Основываясь на следующем ниже, я прочитал еще несколько фрагментов фрагмента большой кучи объектов, и, похоже, это может быть так.

Я видел несколько советов, чтобы выделить большие блоки памяти для таких данных (в моем случае - разные байты []) и управлять памятью в этой области самостоятельно, но это кажется довольно хакерским решением, а не тем Я ожидал бы разрешения проблемы с нестандартным настольным приложением.

Проблема фрагментации вызвана тем фактом (по крайней мере, это то, что многие люди из Microsoft сообщают в блогах), что объекты на LOH не перемещаются во время существования, что понятно, но кажется логичным, что как только некоторое давление памяти будет достигнуто, например, угроза получения OOM, необходимо выполнить перенос.

Единственное, что меня беспокоит, прежде чем полностью доверять тому, что фрагментация является причиной, заключается в том, что так много объектов на LOH без ссылок на gcroot - это потому, что даже для сбора мусора LOH выполняется только частично?

Я буду рад за то, что указал на любое интересное решение, так как в настоящий момент я знаю только о том, как управлять некоторым предварительно выделенным блоком памяти.

Любые идеи приветствуются. Спасибо.

Ответ 1

Как обычно, все оказалось немного по-другому. Мы нашли usecase, где приложение действительно потребляло много памяти и в конечном итоге вышло OOM. Что было странно на дампах, которые мы получили, прежде чем мы обнаружили, что было много объектов без gcroot - я не понимал, почему он не был освобожден и не использовался для новых распределений? Затем мне пришло в голову, что, возможно, что произошло, когда произошел OOM - стек был размотан, а объекты, которые владели памятью, уже не были доступны, а THEN - сброс. Вот почему было много памяти, которая могла бы быть GCed.

Что я сделал в отладочной версии - для получения реального дампа состояния памяти - это создать Threading.Timer, который проверяет, может ли быть выделен какой-то достаточно большой объект - если он не может быть выделен, он является признаком того, что мы близки к OOM и что его хорошее время для хранения дампа памяти. Код следует:

private static void OomWatchDog(object obj)
{
 try                          
 {
   using(System.Runtime.MemoryFailPoint memFailPoint = 
          new System.Runtime.MemoryFailPoint(20))
   {
   }
 }
 catch (InsufficientMemoryException)
 {
   PerformDump();
 }
}

Ответ 2

LOH подвергается фрагментации. Эта статья содержит анализ и основные направления для его работы.
Может быть, вы можете опубликовать код, показывающий "типичное" использование этих байтов [] буферов?

Ответ 3

Иногда Image.FromFile( "файл без изображения" ) выдает исключение OutOfMemoryException. Файл с нулевым байтом - это один из таких файлов.

Ответ 4

Если вы считаете, что проблема LOH заключается в том, что наличие точки останова при распределении LOH может указывать на вас в правильном направлении. Возможно, вы могли бы сделать что-то вроде этого

bp mscorwks! gc_heap:: allocate_large_object "! clrstack;.echo ********* Выделение кучи больших объектов ***********; g"