Каковы наиболее распространенные (и часто упускаемые) причины утечек памяти в управляемых (.net) приложениях?

Пожалуйста, можете ли вы рекомендовать краткое руководство/руководство по лучшей практике, чтобы помочь нам избежать простых (но тонких) ошибок, которые могут вызвать утечку памяти в приложениях .net.

Мне сложно и очень больно начинать поиск причины утечки памяти, когда я на этапе тестирования проекта.

Если есть "правила большого пальца", чтобы полностью руководствоваться утечками памяти в управляемых приложениях, я умоляю вас поделиться своим опытом.

Спасибо.

(Я думал, что управляемые приложения должны быть "управляемыми памятью", то есть GC? Почему же мы все еще находим утечки в чисто управляемом коде?)

Ответ 1

Существует множество форм утечек:

  • Неуправляемые утечки (код, который выделяет неуправляемый код)
  • Утечки ресурсов (код, который выделяет и использует неуправляемые ресурсы, например файлы, сокеты)
  • Увеличенное время жизни объектов
  • Неправильное понимание того, как работает управление памятью GC и .NET.
  • Ошибки в среде выполнения .NET.

Первые два обычно обрабатываются двумя разными фрагментами кода:

  • Реализация IDisposable на объекте и удаление неуправляемой памяти/ресурса в методе Dispose
  • Реализация финализатора, чтобы убедиться, что неуправляемые ресурсы освобождены, когда GC обнаружил, что объект имеет право на сбор

Третий, однако, отличается.

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

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

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

В-пятых, что сложнее, я видел только одну ошибку управления ресурсами в .NET до сих пор, и afaik был намечен для исправления в .NET 4.0, это было с копированием экрана рабочего стола в образ .NET.


Изменить. В ответ на вопрос в комментариях, как избежать хранения ссылок дольше, чем необходимо, тогда единственный способ сделать это - только это.

Позвольте мне объяснить.

Во-первых, если у вас длительный метод (например, он может обрабатывать файлы на диске или загружать что-то или аналогичные), и вы использовали ссылку на большую структуру данных в начале метода, прежде длинная часть, а затем вы не используете эту структуру данных для остальной части метода, тогда .NET в версиях-релизах (и не работает под отладчиком) достаточно умен, чтобы знать, что эта ссылка, хотя она хранится в переменной, имеющей техническую значимость, имеет право на сбор мусора. Сборщик мусора действительно агрессивен в этом отношении. В debug-build и работает под отладчиком, он сохранит ссылку на весь срок службы метода, если вы хотите его проверить, когда остановлен в точке останова.

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

Ответ 2

Номер один будет обработчиком событий, которые прикреплены и никогда не будут отсоединены.

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

Ответ 3

Чтобы ответить на два последних вопроса:

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

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

Итак, два эмпирических правила:

  • Отмените регистрацию любого обработчика события при выпуске объекта или используйте слабую ссылку для него (поиск по SO, где он объясняется).
  • Если класс предоставляет метод Dispose, вызовите его. Если вы используете объект только временно, используйте конструкцию using. Если у вас есть члены, реализующие IDisposable, реализуйте IDisposable самостоятельно и вызывайте "Dispose" в Dispose.

Ответ 4

Краткий ответ - неочевидные ссылки.

Чтобы добавить некоторые детали:

  • Статика не собирается до тех пор, пока не будет собрана AppDomain (что может привести к отключению процесса)
  • События (не забудьте отписаться)
  • Заблокированные финализаторы (финализаторы запускаются последовательно, поэтому любой финализатор блокировки предотвращает сбор всех других финализируемых объектов). Пример включает финализаторы, которые не могут попасть в поток STA.
  • Deadlocked thread никогда не будет выпускать корни
  • Забывание вызова Monitor.Exit() (например, при использовании с таймаутом или между методами) может вызвать тупик, который, в свою очередь, может вызвать утечку

Ответ 5

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

Вызов на любой объект, который реализует IDisposable, где это возможно, особенно в объекте DataSet или DataTable.

Лучше использовать использование {} конструкций на этих объектах.

Ответ 6

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

Но когда утечка реальна, основной причиной могут быть ссылки на объекты, которые поддерживают эти объекты, когда они действительно не нужны. Реализация инструкции using почти для всех объектов, реализующих IDisposable, является хорошим началом. Единственное исключение, о котором я знаю, это прокси-классы WCF, к которым следует обращаться с помощью try/catch/finally, а не с помощью оператора using.

Ответ 7

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

Ответ 8

Циркулярные ссылки в классах значений (обычно в модельном слое вашего приложения) также могут скрыть свои утечки. Теоретически, не делайте их, на практике, осознавайте, когда вам нужно их делать:)

Ответ 9

Проверьте наличие статических переменных, которые не должны присутствовать в памяти после использования, проверьте ссылки на обработку событий, любые финализаторы, которые блокируют сбор других финализаторов, а также если есть какие-либо неуправляемые ссылки на любые DLL или Объекты COM +

Ответ 10

Ниже приведены основные причины утечки памяти.

  • Сохранение ссылок на управляемые объекты (классический случай обработчика событий не выпущен.)
  • Неспособность освободить неуправляемые ресурсы
  • Не удалось удалить объекты чертежа
  • Я заметил, что таймер также вызывает утечку памяти. Утилизируйте его, если вы используете какой-либо таймер.

пожалуйста, прочтите далее http://blogs.msdn.com/b/davidklinems/archive/2005/11/16/three-common-causes-of-memory-leaks-in-managed-applications.aspx