Отладка утечки памяти в .NET
Эта статья относится к: ✔️ пакету SDK для .NET Core 3.1 и более поздних версий
Память может утечь, когда приложение ссылается на объекты, которые больше не требуется выполнять нужную задачу. Ссылка на эти объекты предотвращает восстановление используемой памяти сборщиком мусора. Это может привести к снижению производительности и возникновению OutOfMemoryException исключения.
В этом руководстве показаны средства для анализа утечки памяти в приложении .NET с помощью средств командной строки .NET диагностика. Если вы находитесь в Windows, вы можете использовать средства диагностики памяти Visual Studio для отладки утечки памяти.
В этом руководстве используется пример приложения, которое намеренно утечки памяти в качестве упражнения. Вы также можете анализировать приложения, которые непреднамеренно утечки памяти.
При работе с этим руководством вы сделаете следующее:
- Изучение использования управляемой памяти с помощью dotnet-counters.
- Создание файла дампа.
- Анализ использования памяти с помощью файла дампа.
Необходимые компоненты
В руководстве используются следующие ресурсы:
- Пакет SDK для .NET Core 3.1 или более поздней версии.
- dotnet-counters для проверки использования управляемой памяти.
- dotnet-dump для сбора и анализа файла дампа (включает расширение отладки SOS).
- Пример целевого приложения отладки для диагностики.
В этом руководстве предполагается, что примеры приложений и инструментов установлены и готовы к использованию.
Изучение использования управляемой памяти
Прежде чем приступить к сбору диагностических данных, чтобы помочь в первопричине этого сценария, убедитесь, что вы на самом деле видите утечку памяти (рост использования памяти). Для этого используйте dotnet-counters.
Откройте окно консоли и перейдите к каталогу, в который вы скачали и распаковали пример целевого объекта отладки. Запустите целевой объект:
dotnet run
В отдельном окне консоли найдите идентификатор процесса:
dotnet-counters ps
Вы должны получить примерно такой результат:
4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios
Теперь проверьте использование управляемой памяти с помощью инструмента dotnet-counters. --refresh-interval
задает время между обновлениями (в секундах):
dotnet-counters monitor --refresh-interval 1 -p 4807
Динамические выходные данные должны выглядеть следующим образом:
Press p to pause, r to resume, q to quit.
Status: Running
[System.Runtime]
# of Assemblies Loaded 118
% Time in GC (since last GC) 0
Allocation Rate (Bytes / sec) 37,896
CPU Usage (%) 0
Exceptions / sec 0
GC Heap Size (MB) 4
Gen 0 GC / sec 0
Gen 0 Size (B) 0
Gen 1 GC / sec 0
Gen 1 Size (B) 0
Gen 2 GC / sec 0
Gen 2 Size (B) 0
LOH Size (B) 0
Monitor Lock Contention Count / sec 0
Number of Active Timers 1
ThreadPool Completed Work Items / sec 10
ThreadPool Queue Length 0
ThreadPool Threads Count 1
Working Set (MB) 83
Рассмотрите подробнее эту строку:
GC Heap Size (MB) 4
Как видите, сразу после запуска объем управляемой динамической памяти составляет 4 МБ.
Теперь перейдите по URL-адресу https://localhost:5001/api/diagscenario/memleak/20000
.
Обратите внимание, что объем использования памяти увеличился до 30 МБ.
GC Heap Size (MB) 30
Просмотрев данные об использовании памяти, вы можете точно определить, что происходит: утечка или увеличение памяти. Следующим шагом является сбор правильных данных для анализа памяти.
Создание дампа памяти
При анализе возможных утечек памяти необходимо получить доступ к куче памяти приложения для анализа содержимого памяти. Глядя на связи между объектами, вы создаете теории о том, почему память не освобождается. Распространенный источник диагностических данных — это дамп памяти в Windows или эквивалентный основной дамп в Linux. Для создания дампа приложения .NET можно использовать средство dotnet-dump .
Выполните приведенную ниже команду, чтобы создать основной дамп в Linux для предварительно запущенного примера целевого объекта отладки:
dotnet-dump collect -p 4807
В результате в той же папке будет создан основной дамп.
Writing minidump with heap to ./core_20190430_185145
Complete
Примечание.
Для сравнения с течением времени позвольте исходному процессу продолжить работу после сбора первого дампа и сбора второго дампа таким же образом. Затем у вас будет два дампа за период времени, который можно сравнить, чтобы увидеть, где растет потребление памяти.
Перезапуск неисправного процесса
После сбора дампа у вас должно быть достаточно данных для диагностики неисправного процесса. Если неисправный процесс запущен на рабочем сервере, это удачный момент для выполнения краткосрочного исправления проблем путем перезапуска процесса.
Вы уже завершили работу с примером целевого объекта отладки в рамках этого учебника и можете закрыть этот объект. Перейдите к терминалу, с которого запущен сервер, и нажмите клавиши CTRL+C.
Анализ основного дампа
Теперь, когда у вас есть основной дамп, используйте инструмент dotnet-dump, чтобы проанализировать его:
dotnet-dump analyze core_20190430_185145
Где core_20190430_185145
— это имя основного дампа, который нужно проанализировать.
Примечание.
Если отображается сообщение о том, что libdl.so не удалось найти, возможно, потребуется установить пакет libc6-dev. Дополнительные сведения см. в разделе "Предварительные требования для .NET" в Linux.
Появится запрос, в котором можно ввести команды SOS . Как правило, в первую очередь нужно просмотреть общее состояние управляемой динамической памяти:
> dumpheap -stat
Statistics:
MT Count TotalSize Class Name
...
00007f6c1eeefba8 576 59904 System.Reflection.RuntimeMethodInfo
00007f6c1dc021c8 1749 95696 System.SByte[]
00000000008c9db0 3847 116080 Free
00007f6c1e784a18 175 128640 System.Char[]
00007f6c1dbf5510 217 133504 System.Object[]
00007f6c1dc014c0 467 416464 System.Byte[]
00007f6c21625038 6 4063376 testwebapi.Controllers.Customer[]
00007f6c20a67498 200000 4800000 testwebapi.Controllers.Customer
00007f6c1dc00f90 206770 19494060 System.String
Total 428516 objects
В нашем примере видно, что большинство объектов принадлежат к типу String
либо Customer
.
Вы можете повторно выполнить команду dumpheap
с помощью таблицы методов, чтобы получить список всех экземпляров String
:
> dumpheap -mt 00007f6c1dc00f90
Address MT Size
...
00007f6ad09421f8 00007faddaa50f90 94
...
00007f6ad0965b20 00007f6c1dc00f90 80
00007f6ad0965c10 00007f6c1dc00f90 80
00007f6ad0965d00 00007f6c1dc00f90 80
00007f6ad0965df0 00007f6c1dc00f90 80
00007f6ad0965ee0 00007f6c1dc00f90 80
Statistics:
MT Count TotalSize Class Name
00007f6c1dc00f90 206770 19494060 System.String
Total 206770 objects
Теперь можно использовать gcroot
команду в экземпляре System.String
, чтобы узнать, как и почему объект коренится:
> gcroot 00007f6ad09421f8
Thread 3f68:
00007F6795BB58A0 00007F6C1D7D0745 System.Diagnostics.Tracing.CounterGroup.PollForValues() [/_/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/CounterGroup.cs @ 260]
rbx: (interior)
-> 00007F6BDFFFF038 System.Object[]
-> 00007F69D0033570 testwebapi.Controllers.Processor
-> 00007F69D0033588 testwebapi.Controllers.CustomerCache
-> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
-> 00007F6C000148A0 testwebapi.Controllers.Customer[]
-> 00007F6AD0942258 testwebapi.Controllers.Customer
-> 00007F6AD09421F8 System.String
HandleTable:
00007F6C98BB15F8 (pinned handle)
-> 00007F6BDFFFF038 System.Object[]
-> 00007F69D0033570 testwebapi.Controllers.Processor
-> 00007F69D0033588 testwebapi.Controllers.CustomerCache
-> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
-> 00007F6C000148A0 testwebapi.Controllers.Customer[]
-> 00007F6AD0942258 testwebapi.Controllers.Customer
-> 00007F6AD09421F8 System.String
Found 2 roots.
Как видно, String
непосредственно содержится в объекте Customer
и косвенно в объекте CustomerCache
.
Вы можете продолжить разгрузку объектов и увидите, что большинство объектов String
следуют той же модели. На этом этапе в результате исследования получено достаточно информации, чтобы найти основную причину утечки в коде.
Эта общая процедура позволяет определить источник основных утечек памяти.
Очистка ресурсов
В этом учебнике вы запустили пример веб-сервера. Работа этого сервера должна быть завершена, как описано в разделе Перезапуск неисправного процесса.
Вы также можете удалить созданный файл дампа.
См. также
- dotnet-trace для отображения списка процессов
- dotnet-counters для проверки использования управляемой памяти
- dotnet-dump для сбора и анализа файла дампа
- dotnet/diagnostics
- Отладка утечек памяти с помощью Visual Studio