Анатомія витоку пам'яті


172

У перспективі .NET:

  • Що таке витік пам'яті ?
  • Як можна визначити, чи протікає ваша програма? Які ефекти?
  • Як можна запобігти витоку пам'яті?
  • Якщо у вашій програмі є витік пам’яті, чи йде вона, коли процес закінчується чи вбивається? Або витоки пам'яті у вашій програмі впливають на інші процеси в системі навіть після завершення процесу?
  • А як щодо некерованого коду, доступ до якого здійснюється через COM Interop та / або P / Invoke?

Відповіді:


110

Найкраще пояснення, що я бачив, - це глава 7 безкоштовних електронних книг «Основи програмування» .

В основному, у .NET витік пам'яті відбувається, коли посилаються об'єкти вкорінюються, і таким чином неможливо зібрати сміття. Це трапляється випадково, коли ви тримаєтесь за посиланнями, що виходять за межі передбаченої області.

Ви дізнаєтесь, що у вас є витоки, коли ви почнете отримувати OutOfMemoryExceptions, або використання вашої пам’яті виходить за рамки того, що ви очікували ( PerfMon має приємні лічильники пам’яті).

Розуміння моделі пам'яті .NET - ваш найкращий спосіб уникнути цього. Зокрема, розуміючи, як працює сміттєзбірник і як працюють посилання - я знову посилаюсь на розділ 7 електронної книги. Також пам’ятайте про загальні підводні камені, мабуть, найпоширеніші події. Якщо об'єкт зареєстрований подія на об'єкт B , то об'єкт буде дотримуватися навколо , поки об'єкт B не зникне , тому що B містить посилання на A . Рішення - скасувати реєстрацію своїх подій, коли закінчите.

Звичайно, хороший профіль пам'яті дозволить вам побачити ваші графіки об’єктів та вивчити вкладення / посилання ваших об’єктів, щоб побачити, звідки беруться посилання та який кореневий об’єкт відповідає ( профіль мурах з червоними воротами , JetBrains dotMemory, memprofiler - це справді добре вибір, або ви можете використовувати текстові програми WinDbg та SOS , але я настійно рекомендую комерційний / візуальний продукт, якщо ви не справжній гуру).

Я вважаю, що некерований код підпорядковується типовим витокам пам'яті, за винятком того, що спільний посилання управляє сміттєзбірником. Я можу помилитися в цьому останньому пункті.


11
О, тобі подобається книжка? Я бачив, як автор час від часу спливає на stackoverflow.
Джонно Нолан

Деякі об’єкти .NET також можуть вкоренитися і стати непридатними. Все, що є IDisposable, слід утилізувати через це.
kyoryu

1
@kyoryu: Як сам об'єкт корінь?
Андрій Ронеа

2
@Andrei: Я думаю, що запущена нитка - це, мабуть, найкращий приклад вкорінення об'єкта. Об'єкт, який ставить посилання на себе у статичному непублічному місці (наприклад, підписка на статичну подію чи реалізація сингтона шляхом ініціалізації статичного поля), також може вкоренитися, оскільки немає очевидного способу ... ... "викорчовує" його з причалу.
Джефрі Хантін

@Jeffry Це нетрадиційний спосіб описати, що відбувається, і мені це подобається!
Вихід

35

Власне кажучи, витік пам'яті споживає пам'ять, яка більше не використовується програмою.

"Більше не використовується" має більше ніж одне значення, це може означати "не більше посилання на нього", тобто абсолютно не підлягає відновленню, або це може означати, на яке посилаються, підлягають відновленню, невикористані, але програма все одно зберігає посилання. Тільки пізніші стосуються .Net для ідеально керованих об'єктів . Однак не всі класи досконалі, і в якийсь момент основна некерована реалізація може назавжди просочити ресурси для цього процесу.

У всіх випадках програма споживає більше пам’яті, ніж суворо потрібно. Побічні ефекти, залежно від просоченої суми, можуть перейти від жодного, до уповільнення, викликаного надмірним збором, до ряду винятків з пам'яті і, нарешті, до фатальної помилки з подальшим примусовим припиненням процесу.

Ви знаєте, що програма має проблему з пам'яттю, коли моніторинг показує, що все більше пам'яті виділяється вашому процесу після кожного циклу вивезення сміття . У такому випадку ви або зберігаєте занадто багато пам’яті, або якась основна некерована реалізація просочується.

У більшості витоків ресурси відновлюються, коли процес закінчується, однак деякі ресурси не завжди відновлюються в деяких точних випадках, ручки курсорів GDI для цього відомі. Звичайно, якщо у вас є механізм міжпроцесорного зв’язку, пам'ять, виділена в іншому процесі, не буде звільнена, поки цей процес не звільнить його або не припинить.


32

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

Як зрозуміти, чи протікає ваша програма

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

Як запобігти

Були дані інші хороші думки. Я просто додам, що можливо найбільш часто недооціненою причиною витоків пам'яті .NET є додавання обробників подій до об'єктів, не видаляючи їх. Обробник подій, приєднаний до об'єкта, є формою посилання на цей об'єкт, тому запобігає збору навіть після того, як всі інші посилання відійшли. Завжди пам’ятайте, щоб від'єднати обробники подій (використовуючи -=синтаксис у C #).

Чи протікає витік, коли процес закінчується, а що з інтеропом COM?

Коли процес закінчується, вся оперативна пам'ять, відображена в його адресному просторі, відновлюється ОС, включаючи будь-які COM-об’єкти, що обслуговуються з DLL. Порівняно рідко об'єкти COM можуть подаватися з окремих процесів. У цьому випадку, коли ваш процес закінчується, ви все ще можете нести відповідальність за пам'ять, виділену в будь-яких процесах COM-сервера, які ви використовували.


19

Я б визначив витоки пам'яті як об'єкт, не звільняючи всю пам'ять, виділену після її завершення. Я виявив, що це може статися у вашій програмі, якщо ви використовуєте API Windows та COM (тобто некерований код, у якому є помилка або не керується ним правильно), в рамках та в сторонніх компонентах. Я також виявив, що невиправлення після використання певних предметів, таких як ручки, може спричинити проблему.

Я особисто зазнав винятків із пам'яті, які можуть бути спричинені, але не виняткові для витоків пам'яті в точкових мережних програмах. (OOM також може відбуватися після закріплення, див. Закріплення Artical ). Якщо ви не отримуєте помилок OOM або вам потрібно підтвердити, чи викликає це витік пам’яті, то єдиний спосіб - це профілювання вашої програми.

Я також спробую забезпечити наступне:

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

b) Все, що має метод закриття, знову закривається, використовуючи нарешті або використовуючи оператор (хоча я виявив, що використання не завжди закривається залежно від того, якщо ви оголосили об'єкт поза використанням оператора)

c) Якщо ви використовуєте API без керованого коду / windows, після цього вони вирішуються правильно. (деякі мають методи очищення, щоб звільнити ресурси)

Сподіваюся, це допомагає.


19

Якщо вам потрібно діагностувати витік пам'яті в .NET, перевірте ці посилання:

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

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

У цих статтях описано, як створити дамп пам'яті для вашого процесу та як його проаналізувати, щоб ви могли спочатку визначити, чи є ваш витік некерованим чи керованим, і якщо ним керувати, як з’ясувати, звідки він надходить.

Майкрософт також має новіший інструмент для створення відвалів, які замінюють 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

Найкраще пояснення того, як працює сміттєзбірник, є в Jeff Richters CLR через книгу C # , (гл. 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. Деякі новини - ще гірші.


10

Я думаю, що в умовах керованого середовища витік би ви зберігали непотрібне посилання на великий шматок пам'яті навколо.

Абсолютно. Крім того, не використання методу .Dispose () на одноразових об'єктах, коли це доречно, може спричинити протікання пам’яті. Найпростіший спосіб це зробити за допомогою блоку, оскільки він автоматично виконує .Dispose () в кінці:

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

І якщо ви створюєте клас, який використовує некеровані об’єкти, якщо ви не реалізуєте IDisposable правильно, ви можете викликати витоки пам'яті у користувачів свого класу.


9

Усі витоки пам'яті вирішуються шляхом припинення програми.

Витоку достатньої кількості пам'яті, і Операційна система може вирішити проблему від вашого імені.


8

Я погоджуюсь з Бернардом щодо того, що б там не було.

Ви можете профайлювати свою програму, щоб побачити, як вона використовує пам'ять, і визначити, що якщо вона керує великою кількістю пам'яті, коли її не має бути, ви можете сказати, що вона має витік.

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

Некерований код - це його власний звір, і якщо всередині нього є витік, він буде дотримуватися стандартної пам'яті. визначення протікання.


7

Також майте на увазі, що .NET має дві купи, одна з яких - велика купа об'єктів. Я вважаю, що об'єкти розміром приблизно 85 кб або більше розміщені на цій купі. Ця купа має інші життєві правила, ніж звичайна.

Якщо ви створюєте великі структури пам'яті (словники чи списки), було б доцільно шукати, які саме правила є.

Що стосується повернення пам'яті про припинення процесу, якщо ви не працюєте з Win98 або його еквівалентом, все буде повернуто в ОС після закінчення. Виняток становлять речі, які відкриваються крос-процесом, а інший процес все ще має ресурс відкритим.

Об'єкти COM можуть бути складними. Якщо ви завжди використовуєте IDisposeвізерунок, ви будете в безпеці. Але я зіткнувся з декількома збірками interop, які реалізують IDispose. Ключовим тут є дзвінок, Marshal.ReleaseCOMObjectколи ви закінчите з ним. Об'єкти COM все ще використовують стандартний підрахунок посилань COM.


6

Я знайшов .Net Memory Profiler дуже гарною допомогою для пошуку витоків пам'яті в .Net. Це не вільно, як Microsoft CLR Profiler, але на мою думку, він швидший і більш точний. А


1

Одне визначення: Неможливо випустити недосяжну пам'ять, яка більше не може бути виділена новому процесу під час виконання процесу розподілу. В основному це можна вилікувати за допомогою методів GC або виявити за допомогою автоматизованих інструментів.

Для отримання додаткової інформації відвідайте http://all-about-java-and-weblogic-server.blogspot.in/2014/01/what-is-memory-leak-in-java.html .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.