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

173

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

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

Ответы:

110

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

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

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

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

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

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

Карл Сегуин
источник
11
О, ты любишь книги? Я видел, как автор время от времени появлялся на stackoverflow.
Джонно Нолан
Некоторые объекты .NET также могут получить root-права и стать невосприимчивыми. Все, что IDSposable должен быть удален из-за этого.
кёрю
1
@kyoryu: Как сам объект коренится?
Андрей Ринея
2
@ Андрей: я думаю, что работающий поток - это, пожалуй, лучший пример того, как сам объект укореняется. Объект, который помещает ссылку на себя в статическом непубличном месте (например, подписывается на статическое событие или реализует синглтон с помощью инициализации статического поля), может также укорениться сам по себе, так как нет очевидного способа ... гм ... "искоренить" его от швартовки.
Джеффри Хантин
@ Джеффри, это необычный способ описать происходящее, и мне это нравится!
Exitos
35

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

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

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

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

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

Coincoin
источник
32

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

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

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

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

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

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

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

Мартин
источник
19

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

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

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

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

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

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

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

Джон
источник
19

Если вам нужно диагностировать утечку памяти в .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

Эрик З Борода
источник
16

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

Ник
источник
15

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

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

например

SomeExternalClass.Changed += new EventHandler(HandleIt);

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

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

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

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

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

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

Гас Пол
источник
14

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

Бернард
источник
11

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

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

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

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

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

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

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

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

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

neslekkiM
источник
10

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

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

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

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

Seibar
источник
9

Все утечки памяти устраняются завершением программы.

Утечка достаточного объема памяти, и операционная система может решить проблему от вашего имени.

мистифицировать
источник
8

Я согласен с Бернардом в том, что в .net будет утечка памяти.

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

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

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

похлопывание
источник
7

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

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

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

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

Джоэл Люси
источник
6

Я обнаружил, что .Net Memory Profiler очень помогает при обнаружении утечек памяти в .Net. Это не бесплатно, как Microsoft CLR Profiler, но быстрее и более важно, на мой взгляд.

Ларс Труйенс
источник
1

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

Для получения дополнительной информации, пожалуйста, посетите http://all-about-java-and-weblogic-server.blogspot.in/2014/01/what-is-memory-leak-in-java.html .

Хемант Курми
источник