Каковы наиболее распространенные (и часто игнорируемые) причины утечек памяти в управляемых (.net) приложениях? - PullRequest
13 голосов
/ 23 марта 2009

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

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

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

Спасибо.

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

Ответы [ 11 ]

16 голосов
/ 23 марта 2009

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

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

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

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

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

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

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

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

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


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

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

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

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

5 голосов
/ 23 марта 2009

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

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

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

Утечки памяти могут происходить из разных источников.

  • Вы зарегистрировали событие, но забыли отменить его регистрацию.

  • Файлы / соединения были открыты, но не закрыты должным образом.

  • Вызов Dispose () не был реализован должным образом.

  • Вызов Dispose () был как-то обойден; например, между ними возникла исключительная ситуация.

  • Вы столкнулись с тупиком (который может привести к тому, что корневые объекты не будут освобождены).

  • Поток финализатора заблокирован; например, поток STA для объекта COM недоступен.

  • Утечки из неуправляемого кода.

  • Срок службы объекта слишком велик.

  • Имеется круговая ссылка.

  • Утечки могут происходить из среды .NET Runtime (в редких случаях).

  • Утечки также могут появиться в вашей среде тестирования. (В этом случае это это тестовая утечка, а не утечка среды разработки, а тестовая инженеры несут ответственность за ее решение.)

4 голосов
/ 23 марта 2009

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

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

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

4 голосов
/ 23 марта 2009

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

По определению в управляемом коде нет утечек памяти. Существует два вида утечек:

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

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

  • Отмените регистрацию любого обработчика событий, когда вы отпускаете объект, или используете для этого слабую ссылку (поиск по SO, это где-то объяснено).
  • Если класс предоставляет метод Dispose, вызовите его. Если вы используете объект только временно, используйте конструкцию using. Если у вас есть участники, которые реализуют IDisposable, внедрите IDisposable самостоятельно и позвоните членам Dispose в вашем распоряжении.
4 голосов
/ 23 марта 2009

Номер один - это обработчики событий, которые присоединены и никогда не отсоединяются.

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

3 голосов
/ 23 марта 2009

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

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

2 голосов
/ 23 марта 2009

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

1 голос
/ 14 ноября 2014

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

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

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

1 голос
/ 03 января 2014

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...