Анатомия "утечки памяти" - PullRequest
       108

Анатомия "утечки памяти"

168 голосов
/ 01 августа 2008

В перспективе .NET:

  • Что такое утечка памяти ?
  • Как вы можете определить, протекает ли ваше приложение? Каковы эффекты?
  • Как можно предотвратить утечку памяти?
  • Если ваше приложение имеет утечку памяти, оно исчезает, когда процесс завершается или уничтожается? Или утечки памяти в вашем приложении влияют на другие процессы в системе даже после завершения процесса?
  • А как насчет неуправляемого кода, доступного через COM Interop и / или P / Invoke?

Ответы [ 15 ]

110 голосов
/ 01 августа 2008

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

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

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

Понимание модели памяти .NET - ваш лучший способ избежать этого. В частности, понимание того, как работает сборщик мусора и как работают ссылки, - опять же, я отсылаю вас к главе 7 электронной книги. Кроме того, помните об общих подводных камнях, вероятно, наиболее распространенных из них. Если объект A зарегистрирован в событии на объекте B , то объект A будет удерживаться, пока объект B не исчезнет, ​​поскольку B содержит ссылку на A . Решение состоит в том, чтобы отменить регистрацию ваших событий, когда вы закончите.

Конечно, хороший профиль памяти позволит вам увидеть графики ваших объектов и изучить вложение / ссылки на ваши объекты, чтобы увидеть, откуда поступают ссылки и какой корневой объект ответственен ( профиль муравьев red-gate JetBrains dotMemory, memprofiler - действительно хороший выбор, или вы можете использовать только текстовый WinDbg и SOS , но я настоятельно рекомендую коммерческий / визуальный продукт, если вы не настоящий гуру).

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

35 голосов
/ 01 августа 2008

Строго говоря, утечка памяти потребляет память, которая «больше не используется» программой.

«Больше не используется» имеет более одного значения, это может означать «больше нет ссылки на него», то есть полностью не подлежит восстановлению, или это может означать «ссылка, восстановление», неиспользование, но программа все равно сохраняет ссылки. Только последнее применимо к .Net для отлично управляемых объектов . Однако не все классы идеальны, и в какой-то момент базовая неуправляемая реализация может привести к постоянной утечке ресурсов для этого процесса.

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

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

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

32 голосов
/ 15 августа 2008

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

Как понять, протекает ли ваше приложение

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

Как предотвратить

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

Исчезает ли утечка при выходе из процесса, а как насчет COM-взаимодействия?

Когда ваш процесс завершается, вся память, отображаемая в его адресное пространство, возвращается ОС, включая любые COM-объекты, обслуживаемые из DLL. Сравнительно редко COM-объекты могут обслуживаться из отдельных процессов. В этом случае, когда ваш процесс завершается, вы все равно можете отвечать за память, выделенную в любых процессах COM-сервера, которые вы использовали.

19 голосов
/ 15 августа 2008

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

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

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

У Microsoft также есть более новый инструмент для создания аварийных дампов для замены ADPlus, который называется DebugDiag.

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en

19 голосов
/ 01 августа 2008

Я бы определил утечки памяти как объект, не освобождающий всю память, выделенную после его завершения. Я обнаружил, что это может произойти в вашем приложении, если вы используете Windows API и COM (т. Е. Неуправляемый код, в котором есть ошибка или он не обрабатывается должным образом), в инфраструктуре и сторонних компонентах. Я также обнаружил, что неуборка после использования некоторых объектов, таких как ручки, может вызвать проблему.

Я лично перенес исключения из-за нехватки памяти, которые могут быть вызваны, но не исключают утечки памяти в приложениях dot net. (OOM также может прийти от закрепления, см. Pinning Artical ). Если вы не получаете ошибок OOM или вам необходимо подтвердить, является ли утечка памяти причиной ее возникновения, то единственный способ - профилировать ваше приложение.

Я бы также попытался обеспечить следующее:

a) Все, что реализует Idisposable, удаляется либо с помощью блока finally, либо с помощью оператора using, в том числе кистей, ручек и т. Д. (Некоторые люди утверждают, что в дополнение ко всему ничего не устанавливается)

b) Все, что имеет метод close, снова закрывается с помощью оператора finally или оператора using (хотя я обнаружил, что использование не всегда закрывается в зависимости от того, объявлен ли объект вне оператора using)

в) Если вы используете неуправляемый код / ​​Windows API-интерфейсы, после чего они будут корректно обработаны. (у некоторых есть методы очистки для освобождения ресурсов)

Надеюсь, это поможет.

16 голосов
/ 16 августа 2008

Использование CLR Profiler от Microsoft http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en - отличный способ определить, какие объекты хранят память, какой поток выполнения приводит к созданию этих объектов, а также отслеживать, какие объекты находятся в куче (фрагментация LOH и т. Д.).

15 голосов
/ 27 августа 2008

Лучшее объяснение того, как работает сборщик мусора, приведено в книге Джеффа Рихтерса CLR via C # (гл. 20). Чтение этого дает хорошую основу для понимания того, как объекты сохраняются.

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

, например

SomeExternalClass.Changed += new EventHandler(HandleIt);

и не забудьте отсоединить его при утилизации, тогда SomeExternalClass имеет ссылку на ваш класс.

Как упоминалось выше, SciTech memory profiler отлично показывает корни объектов, которые, как вы подозреваете, протекают.

Но есть также очень быстрый способ проверить определенный тип, просто используйте WnDBG (вы можете даже использовать его в окне немедленного доступа VS.NET, когда он подключен):

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

Теперь сделайте то, что, по вашему мнению, удалит объекты этого типа (например, закройте окно). Здесь удобно иметь кнопку отладки, которая будет запускаться System.GC.Collect() пару раз.

Затем снова запустите !dumpheap -stat -type <TypeName>. Если число не уменьшилось или не снизилось так сильно, как вы ожидаете, у вас есть основания для дальнейшего расследования. (Я получил этот совет на семинаре, который дал Инго Раммер ).

14 голосов
/ 01 августа 2008

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

11 голосов
/ 01 сентября 2008

Почему люди думают, что утечка памяти в .NET отличается от любой другой утечки?

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

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

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

Когда кодирование выполняется неуправляемо, вы обычно убираетесь, вы знаете, что ресурсы, за которые вы беретесь, будут вашими обязанностями, а не уборщиком.

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

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

Я вижу, что у многих это есть, и я очень надеюсь, что это закончится. Вы не можете попросить пользователя закрыть ваше приложение, чтобы навести порядок! Посмотрите на браузер, который может быть IE, FF и т. Д., Затем откройте, скажем, Google Reader, оставьте его на несколько дней и посмотрите, что произойдет.

Если вы затем откроете другую вкладку в браузере, перейдете на какой-либо сайт, а затем закроете вкладку, на которой размещена другая страница, из-за которой произошла утечка в браузере, вы думаете, браузер освободит память? Не так с IE. На моем компьютере IE легко съест 1 ГБ памяти за короткий промежуток времени (около 3-4 дней), если я использую Google Reader. Некоторые газеты еще хуже.

10 голосов
/ 06 августа 2008

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

Абсолютно. Кроме того, неиспользование метода .Dispose () для одноразовых объектов, когда это уместно, может вызвать утечку памяти. Самый простой способ сделать это - использовать блок using, потому что он автоматически выполняет .Dispose () в конце:

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

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

...