windbg "Свободный" тип объекта - PullRequest
8 голосов
/ 24 августа 2010

Наблюдение за использованием виртуальных байтов моей программы во время ее работы показало, что при выполнении каких-то операций использование виртуальных байтов увеличивается примерно на 1 ГБ примерно за 5 минут. Программа имеет дело с сокетами tcp и высокой пропускной способностью передачи данных между ними (~ 800 Мбит / с).

Загрузка файла дампа программы в windbg показала, что причина очень высокого и быстрого использования памяти - около 1 ГБ «свободных» объектов. Действительно, когда я вызываю сборщик мусора (gen 0, 1 и 2) с экрана консоли программы (после перехода в это состояние), он освобождает около 1 ГБ памяти.

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

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

Другое предположение заключалось в том, что, возможно, «Куча 2-го поколения» фрагментируется из-за закрепленных объектов, но если бы это было так, GC.Collect не решит проблему, но на самом деле это так, поэтому я считаю, что это не так. *

Что я подозреваю из беседы с Полом, так это то, что память освобождается, но по какой-то причине возвращается в ОС редко или только когда я вручную вызываю GC.Collect.

Ответы [ 2 ]

7 голосов
/ 24 августа 2010

Они не являются свободными «объектами», они являются свободным пространством. .NET не освобождает память, которую она немедленно использовала обратно в операционную систему. Любые свободные блоки можно использовать для последующего выделения объектов, при условии, что они помещаются внутри свободного блока (в противном случае необходимо увеличить кучу, попросив операционную систему выделить больше памяти).

Сборщик мусора старается объединить свободное пространство в большие пригодные для использования блоки путем сжатия поколения 2. Это не всегда возможно: например, приложение может закреплять объекты, которые потенциально могут помешать сборщику мусора объединить свободное пространство, перемещая живые объекты к передней части кучи. Если это часто случается, память приложения будет разбита на бесполезные маленькие блоки, и этот эффект известен как «фрагментация кучи».

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

Я бы предложил запустить dumpheap -stat. Эта команда сообщит в конце списка о любых фрагментированных блоках. Затем вы можете сбросить эти блоки, чтобы получить представление о том, что происходит.

5 голосов
/ 25 августа 2010

Кстати, похоже, у вас есть хорошо известная проблема (по крайней мере, среди гуру сокетов), которую большинство сокет-серверов получают в .Net. Павел уже коснулся того, что это значит.Более подробно, что происходит неправильно, во время чтения / записи в сокет буфер буферизуется - это означает, что GC не разрешено перемещать его (как таковой, ваши фрагменты кучи).Coversant (который был пионером решения) видел исключение OutOfMemoryException, когда их фактическое использование памяти составляло всего около 500 МБ (из-за такой сильной фрагментации).Исправление - это совсем другая история.

Что вы хотите сделать, это при запуске приложения выделить очень большое количество буферов (в настоящее время я занимаю 50 МБ).Вы найдете новые классы ArraySegment<T> (v2.0) и ConcurrentQueue<T> (v4.0) особенно полезными при написании этого.Если вы еще не используете v4.0, есть несколько очередей без блокировки, плавающих в трубах.

// Pseudo-code.
ArraySegment<byte> CheckOut()
{
  ArraySegment<byte> result;
  while(!_queue.TryDequeue(out result))
    GrowBufferQueue(); //Enqueue a bunch more buffers.
  return result;
}

void CheckOut(ArraySegment<byte> buffer)
{
  _queue.Enqueue(buffer);
}

void GrowBufferQueue()
{
  // Verify this, I did throw it together in 30s.
  // Allocates nearly 2MB. You might want to tweak that.
  for(var j = 0; j < 5; j++)
  {
    var buffer = new byte[409600]; // 4096 = page size on Windows.
    for(var i = 0; i < 409600; i += 4096)
      _queue.Enqueue(new ArraySegment<byte>(buffer, i, 4096));
  }
}

После этого вам нужно будет создать подкласс NetworkStream и заменить входящий буфер однимиз вашего пула буферов.Buffer::BlockCopy поможет производительности ( не использовать Array::Copy).Это сложно и волосато;особенно если вы делаете его асинхронным.

Если вы не используете многоуровневые потоки (например, SSLStream <-> DeflateStream <-> XmlWriter и т. д.), вы должны использовать новый шаблон асинхронного сокета в .Net 4.0 ;который имеет большую эффективность около IAsyncResult с, которые проходят вокруг.Поскольку вы не разделяете потоки, у вас есть полный контроль над буферами, которые используются - поэтому вам не нужно идти по маршруту подкласса NetworkStream.

...