C # .NET Сборка мусора не работает? - PullRequest
8 голосов
/ 16 июня 2011

Я работаю над относительно большим решением в Visual Studio 2010. У него есть несколько проектов, один из которых - проект XNA Game, а другой - проект ASP.NET MVC 2.

В обоих проектах я сталкиваюсь с одной и той же проблемой: после запуска их в режиме отладки использование памяти продолжает расти. Они начинаются с 40 и 100 МБ памяти соответственно, но оба увеличиваются до 1,5 ГБ относительно быстро (10 и 30 минут соответственно). После этого он иногда возвращался к исходному использованию, а иногда просто выбрасывал OutOfMemoryExceptions.

Конечно, это указывало бы на серьезные утечки памяти, и именно здесь я изначально пытался определить проблему. После безуспешного поиска утечек я пытался регулярно звонить GC.Collect() (примерно раз в 10 секунд). После введения этого «хака» использование памяти оставалось на 45 и 120 МБ соответственно в течение 24 часов (пока я не прекратил тестирование).

Сборка мусора .NET должна быть "очень хорошей", но я не могу не подозревать, что она просто не выполняет свою работу. Я использовал CLR Profiler в попытке решить проблему, и он показал, что проект XNA, похоже, сохранил много байтовых массивов, которые я действительно использовал, но ссылки на которые уже должны быть удалены и поэтому собраны мусором. коллектор.

Опять же, когда я регулярно звоню GC.Collect(), проблемы с использованием памяти, похоже, исчезли. Кто-нибудь знает, что может быть причиной такого высокого использования памяти? Возможно, это связано с работой в режиме отладки?

Ответы [ 6 ]

13 голосов
/ 16 июня 2011

После безуспешного поиска утечек

Попробуй сильнее =)

Утечки памяти на управляемом языке могут быть сложными, чтобы отследить. У меня был хороший опыт работы с Redgate ANTS Memory Profiler . Это не бесплатно, но они дают вам 14-дневную полнофункциональную пробную версию. Он имеет приятный пользовательский интерфейс и показывает, где находится ваша память и почему эти объекты хранятся в памяти.

Как говорит Алекс, обработчики событий являются очень распространенным источником утечек памяти в приложении .NET. Учтите это:

public static class SomeStaticClass
{
    public event EventHandler SomeEvent;
}

private class Foo
{
    public Foo()
    {
        SomeStaticClass.SomeEvent += MyHandler;
    }

    private void MyHandler( object sender, EventArgs ) { /* whatever */ }
}

Я использовал статический класс, чтобы сделать проблему как можно более очевидной. Допустим, в течение жизни вашего приложения создается много объектов Foo. Каждый Foo подписывается на событие SomeEvent статического класса.

Объекты Foo могут время от времени выпадать из области видимости, но статический класс поддерживает ссылку на каждый из них через делегат обработчика событий. Таким образом, они сохраняются живыми до бесконечности. В этом случае обработчик событий просто необходимо отключить.

... проект XNA, похоже, сохранил много байтовых массивов, которые я действительно использовал ...

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

Профилировщик, на которого я ссылался выше, сообщит вам, если это проблема. Если это так, вы, вероятно, сможете отследить его до утечки объекта где-нибудь. Я только что исправил проблему в моем приложении, показывающую то же поведение, и это было из-за того, что MemoryStream не выпускал свой внутренний byte[] даже после вызова Dispose() для него. Оборачивание потока в фиктивный поток и обнуление его решило проблему.

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

Мое предложение; это не GC, проблема в вашем приложении. Используйте профилировщик, приведите ваше приложение в состояние высокого потребления памяти, сделайте снимок памяти и начните анализ.

5 голосов
/ 16 июня 2011

В первую очередь, GC работает и работает хорошо.В этом нет ошибки, которую вы только что обнаружили.

Теперь, когда мы ушли от этого, некоторые мысли:

  1. Используете ли вы слишком много потоков?
  2. Помните, что GC недетерминирован;он будет запускаться всякий раз, когда сочтет необходимым (даже если вы звоните GC.Collect().
  3. Вы уверены, что все ваши ссылки выходят за рамки?
  4. Что вы загружаете в памятьво-первых, «Большие изображения», «Большие текстовые файлы»

Ваш профилировщик должен сказать вам, что использует столько памяти. Начните резать на самых больших преступниках столько, сколько сможете.

Кроме того, вызывать GC.Collect () каждые X секунд - плохая идея и вряд ли решит вашу реальную проблему.

4 голосов
/ 16 июня 2011

Анализ проблем с памятью в .NET не является тривиальной задачей, и вам определенно следует прочитать несколько хороших статей и попробовать разные инструменты для достижения результата.После исследований я получаю следующую статью: http://www.alexatnet.com/content/net-memory-management-and-garbage-collector Вы также можете попробовать прочитать некоторые статьи Джеффри Рихтера, например: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

По моему опыту, есть два наиболее распространенныхпричины проблемы нехватки памяти:

  1. Обработчики событий - они могут содержать объект, даже если другие объекты не ссылаются на него.Поэтому в идеале вам нужно отписаться от обработчиков событий, чтобы автоматически уничтожить объект.
  2. Поток финализатора заблокирован другим потоком в режиме STA.Например, когда поток STA выполняет большую работу, другие потоки останавливаются и объекты, находящиеся в очереди завершения, не могут быть уничтожены.
3 голосов
/ 16 июня 2011

Редактировать: добавлена ​​ссылка на фрагментация кучи больших объектов .

Edit: так как похоже, что это проблема с выделением и выбрасыванием текстур, можете ли вы использовать Texture2D.SetData для повторного использования больших байтов [] s?

Во-первых, вам необходимо выяснить, протекает ли управляемая или неуправляемая память.

  1. Используйте perfmon, чтобы увидеть, что происходит с вашим процессом '.net memory # Bytes in all Heaps' и Process\Private Bytes. Сравните цифры и память возрастет. Если увеличение количества приватных байтов опережает увеличение объема динамической памяти, это означает рост неуправляемой памяти.

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

  3. Если это управляемый рост памяти, то нам нужно увидеть, какое поколение / LOH (есть также счетчики производительности для каждого поколения байтов кучи).

  4. Если это байты кучи больших объектов, вам нужно пересмотреть использование и выбрасывание больших байтовых массивов. Возможно, байтовые массивы можно использовать повторно, а не отбрасывать. Кроме того, рассмотрите возможность выделения больших байтовых массивов с степенями 2. Таким образом, при удалении вы оставите большую «дыру» в куче больших объектов, которая может быть заполнена другим объектом того же размера.

  5. Последнее беспокойство связано с памятью, но у меня нет для вас никаких советов по этому поводу, потому что я никогда с ней не связывался.

1 голос
/ 16 июня 2011

Я бы также добавил, что если вы делаете какой-либо доступ к файлу, убедитесь, что вы закрываете и / или утилизируете любые устройства чтения или записи. Вы должны иметь соответствие 1-1 между открытием любого файла и его закрытием.

Кроме того, я обычно использую предложение using для ресурсов, например, Sql Connection:

using (var connection = new SqlConnection())
{
  // Do sql connection work in here.
}

Реализуете ли вы IDisposable на каких-либо объектах и, возможно, делаете что-то нестандартное, что вызывает проблемы? Я бы дважды проверил весь ваш IDisposable код.

0 голосов
/ 20 октября 2016

GC не учитывает неуправляемую кучу. Если вы создаете множество объектов, которые являются просто оболочками в C #, для большей неуправляемой памяти, то ваша память пожирается, но GC не может принимать рациональные решения на основе этого, поскольку он видит только управляемую кучу.

Вы попадаете в ситуацию, когда сборщик ГХ не думает, что вам не хватает памяти, потому что большинство вещей в вашей куче 1-го поколения - это 8-байтовые ссылки, где на самом деле они похожи на айсберги в море. Большая часть памяти ниже!

Вы можете использовать эти вызовы GC: * ​​1005 *

System::GC::AddMemoryPressure(sizeOfField);
System::GC::RemoveMemoryPressure(sizeOfField);

Эти методы позволяют сборщику мусора видеть неуправляемую память (если вы предоставите ей правильные цифры)

...