OutOfMemory, но для многих объектов нет gcroots - PullRequest
11 голосов
/ 11 октября 2009

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

Memory Profiler показал только 130 МБ в экземплярах живых объектов. Интересно то, что для многих типов объектов было показано очень большое количество недоступных экземпляров (например, 22000 недоступных экземпляров Byte []). В исходной статистике памяти она составляет 127 МБ во всех кучах для данных (что нормально), но указывает на недоступность 133 МБ в куче второго поколения и 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)

А также проверка трассировки стека потоков собственного финализатора показывает, что она не заблокирована.

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

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

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

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

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

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

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

Ответы [ 4 ]

4 голосов
/ 11 октября 2009

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

2 голосов
/ 12 октября 2009

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

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

private static void OomWatchDog(object obj)
{
 try                          
 {
   using(System.Runtime.MemoryFailPoint memFailPoint = 
          new System.Runtime.MemoryFailPoint(20))
   {
   }
 }
 catch (InsufficientMemoryException)
 {
   PerformDump();
 }
}
1 голос
/ 27 октября 2009

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

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

1 голос
/ 11 октября 2009

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

...