Учебник. Отладка утечки памяти в .NET CoreTutorial: Debug a memory leak in .NET Core

Эта статья относится к ✓ SDK для .NET Core 3.0 и более поздних версийThis article applies to: ✓ .NET Core 3.0 SDK and later versions

В этом учебнике демонстрируются инструменты, позволяющие проанализировать утечку памяти в .NET Core.This tutorial demonstrates the tools to analyze a .NET Core memory leak.

Здесь используется пример приложения, в котором намеренно происходит утечка памяти.This tutorial uses a sample app, which is designed to intentionally leak memory. Пример предоставляется для выполнения упражнения.The sample is provided as an exercise. Вы можете проанализировать приложение, в котором также непреднамеренно происходит утечка памяти.You can analyze an app that is unintentionally leaking memory too.

В этом руководстве рассмотрены следующие задачи:In this tutorial, you will:

  • Изучение использования управляемой памяти с помощью dotnet-counters.Examine managed memory usage with dotnet-counters.
  • Создание файла дампа.Generate a dump file.
  • Анализ использования памяти с помощью файла дампа.Analyze the memory usage using the dump file.

Предварительные требованияPrerequisites

В этом учебнике используется:The tutorial uses:

В учебнике предполагается, что пример приложения и инструменты установлены и готовы к использованию.The tutorial assumes the sample and tools are installed and ready to use.

Изучение использования управляемой памятиExamine managed memory usage

Перед началом сбора данных диагностики с целью поиска основной причины, которая привела к данному сценарию, необходимо убедиться в наличии утечки памяти (или ее увеличения).Before you start collecting diagnostics data to help us root cause this scenario, you need to make sure you're actually seeing a memory leak (memory growth). Для этого используйте dotnet-counters.You can use the dotnet-counters tool to confirm that.

Откройте окно консоли и перейдите к каталогу, в который вы скачали и распаковали пример целевого объекта отладки.Open a console window and navigate to the directory where you downloaded and unzipped the sample debug target. Запустите целевой объект:Run the target:

dotnet run

В отдельном окне консоли найдите идентификатор процесса с помощью инструмента dotnet-trace.From a separate console, find the process ID using the dotnet-trace tool:

dotnet-trace ps

Результат должен выглядеть следующим образом:The output should be similar to:

4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios

Теперь проверьте использование управляемой памяти с помощью инструмента dotnet-counters.Now, check managed memory usage with the dotnet-counters tool. --refresh-interval задает время между обновлениями (в секундах):The --refresh-interval specifies the number of seconds between refreshes:

dotnet-counters monitor --refresh-interval 1 -p 4807

Динамические выходные данные должны выглядеть следующим образом:The live output should be similar to:

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

Рассмотрите подробнее эту строку:Focusing on this line:

    GC Heap Size (MB)                                  4

Как видите, сразу после запуска объем управляемой динамической памяти составляет 4 МБ.You can see that the managed heap memory is 4 MB right after startup.

Теперь перейдите по URL-адресу http://localhost:5000/api/diagscenario/memleak/20000.Now, hit the URL http://localhost:5000/api/diagscenario/memleak/20000.

Обратите внимание, что объем использования памяти увеличился до 30 МБ.Observe that the memory usage has grown to 30 MB.

    GC Heap Size (MB)                                 30

Просмотрев данные об использовании памяти, вы можете точно определить, что происходит: утечка или увеличение памяти.By watching the memory usage, you can safely say that memory is growing or leaking. Следующим шагом является сбор правильных данных для анализа памяти.The next step is to collect the right data for memory analysis.

Создание дампа памятиGenerate memory dump

При анализе возможных утечек памяти необходимо получить доступ к динамической памяти приложения.When analyzing possible memory leaks, you need access to the app's memory heap. Затем можно анализировать содержимое памяти.Then you can analyze the memory contents. Изучив связи между объектами, можно делать предположения о том, почему область памяти не освобождается.Looking at relationships between objects, you create theories on why memory isn't being freed. Общий источник диагностических данных — это дамп памяти в Windows или эквивалентный основной дамп в Linux.A common diagnostics data source is a memory dump on Windows or the equivalent core dump on Linux. Чтобы создать дамп приложения .NET Core, воспользуйтесь инструментом dotnet-dump.To generate a dump of a .NET Core application, you can use the dotnet-dump) tool.

Выполните приведенную ниже команду, чтобы создать основной дамп в Linux для предварительно запущенного примера целевого объекта отладки:Using the sample debug target previously started, run the following command to generate a Linux core dump:

dotnet-dump collect -p 4807

В результате в той же папке будет создан основной дамп.The result is a core dump located in the same folder.

Writing minidump with heap to ./core_20190430_185145
Complete

Перезапуск неисправного процессаRestart the failed process

После сбора дампа у вас должно быть достаточно данных для диагностики неисправного процесса.Once the dump is collected, you should have sufficient information to diagnose the failed process. Если неисправный процесс запущен на рабочем сервере, это удачный момент для выполнения краткосрочного исправления проблем путем перезапуска процесса.If the failed process is running on a production server, now it's the ideal time for short-term remediation by restarting the process.

Вы уже завершили работу с примером целевого объекта отладки в рамках этого учебника и можете закрыть этот объект.In this tutorial, you're now done with the Sample debug target and you can close it. Перейдите к терминалу, с которого запущен сервер, и нажмите клавиши Control-C.Navigate to the terminal that started the server and press Control-C.

Анализ основного дампаAnalyze the core dump

Теперь, когда у вас есть основной дамп, используйте инструмент dotnet-dump, чтобы проанализировать его:Now that you have a core dump generated, use the dotnet-dump) tool to analyze the dump:

dotnet-dump analyze core_20190430_185145

Где core_20190430_185145 — это имя основного дампа, который нужно проанализировать.Where core_20190430_185145 is the name of the core dump you want to analyze.

Примечание

Если отображается сообщение о том, что libdl.so не удалось найти, возможно, потребуется установить пакет libc6-dev.If you see an error complaining that libdl.so cannot be found, you may have to install the libc6-dev package. Дополнительные сведения см. в статье Необходимые компоненты для .NET Core в Linux.For more information, see Prerequisites for .NET Core on Linux.

Отобразится командная строка для ввода команд SOS.You'll be presented with a prompt where you can enter SOS commands. Как правило, в первую очередь нужно просмотреть общее состояние управляемой динамической памяти:Commonly, the first thing you want to look at is the overall state of the managed heap:

> 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.Here you can see that most objects are either String or Customer objects.

Вы можете повторно выполнить команду dumpheap с помощью таблицы методов, чтобы получить список всех экземпляров String:You can use the dumpheap command again with the method table (MT) to get a list of all the String instances:

> dumpheap -mt 00007faddaa50f90

         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, чтобы узнать, как и зачем объект становится корневым.You can now use the gcroot command on a System.String instance to see how and why the object is rooted. Подождите, так как выполнение этой команды для объема памяти в 30 МБ занимает несколько минут:Be patient because this command takes several minutes with a 30-MB heap:

> gcroot -all 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.You can see that the String is directly held by the Customer object and indirectly held by a CustomerCache object.

Вы можете продолжить разгрузку объектов и увидите, что большинство объектов String следуют той же модели.You can continue dumping out objects to see that most String objects follow a similar pattern. На этом этапе в результате исследования получено достаточно информации, чтобы найти основную причину утечки в коде.At this point, the investigation provided sufficient information to identify the root cause in your code.

Эта общая процедура позволяет определить источник основных утечек памяти.This general procedure allows you to identify the source of major memory leaks.

Очистка ресурсовClean up resources

В этом учебнике вы запустили пример веб-сервера.In this tutorial, you started a sample web server. Работа этого сервера должна быть завершена, как описано в разделе Перезапуск неисправного процесса.This server should have been shut down as explained in the Restart the failed process section.

Вы также можете удалить созданный файл дампа.You can also delete the dump file that was created.

Следующие шагиNext steps

Поздравляем с завершением этого учебника!Congratulations on completing this tutorial.

Мы все еще публикуем дополнительные диагностические учебники.We're still publishing more diagnostic tutorials. С черновыми версиями можно ознакомиться в репозитории dotnet/diagnostics.You can read the draft versions on the dotnet/diagnostics repository.

В этом учебнике были рассмотрены основные инструменты диагностики .NET.This tutorial covered the basics of key .NET diagnostic tools. Дополнительные сведения о расширенном использовании см. в следующей справочной документации:For advanced usage, see the following reference documentation: