Які стратегії та інструменти корисні для пошуку витоків пам'яті у .NET?


152

Я писав С ++ протягом 10 років. У мене виникли проблеми з пам’яттю, але їх можна було вирішити з розумною витратою зусиль.

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

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

Які стратегії та інструменти корисні для усунення витоків пам'яті у .NET?


Назва публікації насправді не відповідає питанню у вашому дописі. Я пропоную вам оновити назву.
Кевін

Ти маєш рацію. Вибачте, я трохи втомився від поточного витоку, на який я полюю! Назва оновлена
Скотт Ленгем,

3
@Scott: Не надоїдайте .NET, це не проблема. Ваш код є.
ГЕОЧЕТ

3
Так, мій код чи бібліотеки третьої сторони я із задоволенням використовую.
Скотт Ленгем,

@Scott: Дивіться мою відповідь. MemProfiler того вартий. Використання його також дасть вам абсолютно новий рівень розуміння світу .NET GC.
ГЕОЧЕТ

Відповіді:


51

Я використовую MemProfiler Scitech, коли підозрюю про витік пам'яті.

Поки що я вважав це дуже надійним і потужним. Це врятувало моє сало хоча б з одного приводу.

GC дуже добре працює у .NET IMO, але як і будь-яка інша мова чи платформа, якщо ви пишете поганий код, трапляються погані речі.


3
Так, я мав ходити з цим, і це допомогло мені дістати до низу деяких складних витоків. Найбільші витоки, які у мене виявилися, викликані сторонніми бібліотеками в керованому коді, до якого вони отримували доступ через interop. Мене вразило, що цей інструмент виявив витоки в некерованому коді, а також керованому коді.
Скотт Ленгем

1
Я прийняв це як відповідь, тому що це те, що працювало для мене, врешті-решт, але я думаю, що всі інші відповіді дуже корисні. До речі, цей інструмент частіше називають Mem Profiler SciTech!
Скотт Ленгем

41

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

    public void Dispose ()
    {
        // Dispose logic here ...

        // It's a bad error if someone forgets to call Dispose,
        // so in Debug builds, we put a finalizer in to detect
        // the error. If Dispose is called, we suppress the
        // finalizer.
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

#if DEBUG
    ~TimedLock()
    {
        // If this finalizer runs, someone somewhere failed to
        // call Dispose, which means we've failed to leave
        // a monitor!
        System.Diagnostics.Debug.Fail("Undisposed lock");
    }
#endif

Я вважаю за краще кинути виняток замість Debug.Fail
Pedro77

17

У нашому проекті ми використовували програмне забезпечення Ants Profiler Pro від Red Gate. Він працює дуже добре для всіх мовних програм .NET.

Ми з'ясували, що .NET Garbage Collector дуже "безпечний" при очищенні об'єктів пам'яті (як це має бути). Він би зберігав об'єкти навколо нас лише тому, що ми могли б використовувати його коли - небудь в майбутньому. Це означало, що нам потрібно бути більш уважними щодо кількості предметів, які ми надули в пам’яті. Врешті-решт, ми перетворили всі наші об’єкти даних на «завищення за потребою» (безпосередньо перед запитом поля), щоб зменшити накладні витрати та збільшити продуктивність.

EDIT: Ось подальше пояснення того, що я маю на увазі під "надуванням попиту". У нашій об'єктній моделі нашої бази даних ми використовуємо Властивості батьківського об'єкта для викриття дочірніх об'єктів. Наприклад, якби у нас був запис, який посилався на якийсь інший запис "деталей" або "пошуку", один на один, ми б структурували його так:

class ParentObject
   Private mRelatedObject as New CRelatedObject
   public Readonly property RelatedObject() as CRelatedObject
      get
         mRelatedObject.getWithID(RelatedObjectID)
         return mRelatedObject
      end get
   end property
End class

Ми виявили, що вищевказана система створила деякі реальні проблеми з пам'яттю та продуктивністю, коли в пам'яті було багато записів. Таким чином, ми перейшли до системи, де об'єкти надувались лише тоді, коли їх вимагали, а дзвінки до бази даних здійснювались лише за потреби:

class ParentObject
   Private mRelatedObject as CRelatedObject
   Public ReadOnly Property RelatedObject() as CRelatedObject
      Get
         If mRelatedObject is Nothing
            mRelatedObject = New CRelatedObject
         End If
         If mRelatedObject.isEmptyObject
            mRelatedObject.getWithID(RelatedObjectID)
         End If
         return mRelatedObject
      end get
   end Property
end class

Це виявилося набагато ефективнішим, оскільки об’єкти зберігалися в пам'яті, поки вони не знадобляться (доступний метод Get). Це забезпечило дуже високе підвищення продуктивності в обмеженні звернень до бази даних та величезний приріст простору пам’яті.


Я другий цей продукт. Це був один з найкращих профілів, якими я користувався.
Горд

Мені здалося, що профайлер дуже добре розглядає проблеми продуктивності. Однак інструменти аналізу пам’яті були досить бідними. Я знайшов витік за допомогою цього інструменту, але сміття допомагало мені виявити причину витоку. І це зовсім не допоможе вам, якщо витік трапляється в некерованому коді.
Скотт Ленгем

Гаразд, нова версія 5.1 - це чортів набагато краще. Краще допомогти вам знайти причину витоку (хоча - все ще є кілька проблем із цим, які ANTS мені сказали, що вони виправлять у наступній версії). Досі не працює некерований код, але якщо ви не турбуєтесь про некерований код, це зараз досить хороший інструмент.
Скотт Ленгем

7

Ви все ще повинні турбуватися про пам'ять під час написання керованого коду, якщо ваша програма не є тривіальною. Я запропоную дві речі: спочатку прочитайте CLR через C #, оскільки це допоможе вам зрозуміти управління пам'яттю в .NET. По-друге, навчіться використовувати такий інструмент, як CLRProfiler (Microsoft). Це може дати вам уявлення про те, що спричиняє витік пам’яті (наприклад, ви можете подивитися на фрагментацію вашої великої маси об’єктів)


Так. CLRPRofiler досить класний. Він може отримати трохи вибухонебезпечну інформацію при спробі копати вигляд, який він дає виділеним об'єктам, але все є. Це, безумовно, хороша відправна точка, тим більше, що вона безкоштовна.
Скотт Лангхем,

6

Ви використовуєте некерований код? Якщо ви не використовуєте некерований код, на думку Microsoft, витоки пам'яті у традиційному розумінні неможливі.

Пам'ять, яку використовує програма, не може бути звільнена, тому розподіл пам’яті програми може зростати впродовж життя.

З розділу Як визначити витоки пам'яті у загальній мові виконання на веб-сайті Microsoft.com

У програмі .NET Framework може відбуватися витік пам'яті, коли ви використовуєте некерований код як частину програми. Цей некерований код може просочити пам'ять, і час .NET Framework не може вирішити цю проблему.

Крім того, може здатися, що проект має витоку пам'яті. Ця умова може виникнути, якщо багато великих об’єктів (таких як об’єкти DataTable) оголошено і потім додано до колекції (наприклад, DataSet). Ресурси, якими володіють ці об’єкти, ніколи не можуть бути випущені, а ресурси залишаються живими протягом усього запуску програми. Це здається витоком, але насправді це лише симптом способу виділення пам'яті в програмі.

Для вирішення цього виду проблем можна реалізувати IDisposable . Якщо ви хочете ознайомитись із деякими стратегіями управління пам’яттю, я б запропонував шукати IDisposable, XNA, управління пам’яттю, оскільки розробники ігор повинні мати більш передбачуваний збір сміття, і тому змушувати GC робити свою справу.

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


5
Побачити blogs.msdn.com/tess/archive/2006/01/23 / ... . Не дуже важливо, є "витік" пам'яті "традиційним" чи ні, це все-таки витік.
Константин

2
Я бачу вашу думку - але неефективне розподілення та повторне використання пам'яті програмою відрізняється від витоку пам'яті.
Тімоті Лі Рассел

хороша відповідь, дякую, що пам’ятаєте, що обробники подій можуть бути небезпечними.
Frameworkninja

3
@Timothy Lee Russel: Якщо необмежений (1) об'єм пам'яті може залишатися одночасно розподіленим (вкоріненим) після того, як стає непотрібним (2), без того, щоб у системі нічого не було інформації та поштовху, необхідних для своєчасного її викорінення, це витік пам'яті . Навіть якщо пам'ять може колись звільнитися, якщо може накопичитися достатньо непотрібних матеріалів, щоб задушити систему, перш ніж це станеться, це витік. (1) Більше, ніж O (N), N - кількість корисного розподілу; (2) Речі марні, якщо видалення посилань на неї не вплине на функціональність програми.
суперкар

2
@Timothy Lee Russel: Нормальна схема "витоку пам'яті" виникає, коли пам'ять зберігається однією суттю від імені іншої сутності , очікуючи, що вона буде сказана, коли вона більше не потрібна, але остання залишає сутність, не повідомляючи про першу. Суб'єкт, що зберігає пам'ять, насправді не потребує цього, але немає способу визначити це.
supercat

5

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


5

Щойно у мене виникла пам'ять у службі Windows, яку я виправив.

По-перше, я спробував MemProfiler . Я вважаю, що це дуже важко у використанні і зовсім не зручно для користувачів.

Тоді я використав JustTrace, який простіший у використанні та дає вам більше деталей про об’єкти, які не розташовані правильно.

Це дозволило мені вирішити витік пам'яті дуже легко.


3

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

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


3

Я віддаю перевагу дотмеморії від Jetbrains


ви можете бути єдиним :)
HellBaby

Я також спробував це. Я думаю, що це хороший інструмент. Простий у використанні, інформативний. Інтегрується до Visual Studio
redeye

У нашому випадку, при усуненні несправностей з пам'яті, інструмент "Знімок Visual Studio" вийшов з ладу / не зробив знімок. Dotmemory зберігав круті та обробляв кілька знімків розміром 3 Гб із (здавалося б) легкістю.
Майкл Каргл

3

Великі зброї - Інструменти налагодження для Windows

Це дивовижна колекція інструментів. Ви можете аналізувати як керовані, так і некеровані купи з ним, і ви можете це робити в режимі офлайн. Це було дуже зручно для налагодження однієї з наших програм ASP.NET, які продовжували переробляти через надмірне використання пам'яті. Мені довелося лише створити повну пам’ять пам’яті живого процесу, що працює на виробничому сервері, весь аналіз робився в режимі офлайн у WinDbg. (З'ясувалося, якийсь розробник надмірно зловживає зберіганням у сесії пам'яті.)

"Якщо зламано, це ..." У блозі є дуже корисні статті на цю тему.


2

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

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


2

Один з найкращих інструментів - це використання інструментів налагодження для Windows , а також зйомка пам’яті процесу за допомогою adplus , потім використання windbg та плагін sos для аналізу пам’яті процесів, потоків та стеків викликів.

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

Потім проаналізуйте офлайн.


Так, це добре працює, особливо для більш вдосконалених матеріалів або діагностики проблем у звільненому програмному забезпеченні, до якого ви не можете легко приєднати налагоджувач. У цьому блозі є багато порад щодо використання цих інструментів: blogs.msdn.com/tess
Скотт Ленгем,

2

Після одного з моїх виправлень керованої програми у мене було те саме, як, як переконатися, що мій додаток не буде мати однаковий витік пам'яті після моєї наступної зміни, тому я написав щось на зразок структури перевірки випуску об'єктів, будь ласка, подивіться далі пакет NuGet ObjectReleaseVerification . Ви можете знайти зразок тут https://github.com/outcoldman/OutcoldSolutions-ObjectReleaseVerification-Sample , а також інформацію про цей зразок http://outcoldman.ru/uk/blog/show/322


0

Від Visual Studio 2015 розглядають можливість використання нестандартного діагностичного інструменту використання пам'яті для збору та аналізу даних про використання пам'яті.

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


0

один з найкращих інструментів, якими я користувався DotMemory.ви можете використовувати цей інструмент як розширення в VS. Після запуску програми ви зможете проаналізувати кожну частину пам'яті (за об'єктом, ім'ям простору тощо), яку використовує ваш додаток, і зробіть короткий знімок цього , Порівняйте його з іншими SnapShots. DotMemory

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