Ladění nevracení paměti v .NET

Tento článek se vztahuje na: ✔️ .NET Core 3.1 SDK a novější verze

Paměť může dojít k nevracení, když aplikace odkazuje na objekty, které už nepotřebuje k provedení požadované úlohy. Odkazování na tyto objekty brání uvolňování paměti uvolnění využité paměti. To může vést ke snížení výkonu a OutOfMemoryException vyvolání výjimky.

Tento kurz ukazuje nástroje pro analýzu nevracení paměti v aplikaci .NET pomocí nástrojů .NET Diagnostics CLI. Pokud používáte Windows, možná budete moct k ladění nevracení paměti použít diagnostické nástroje sady Visual Studio.

V tomto kurzu se jako cvičení používá ukázková aplikace, která záměrně nevracela paměť. Můžete také analyzovat aplikace, které neúmyslně nevracely paměť.

V tomto kurzu:

  • Prozkoumejte využití spravované paměti pomocí čítačů dotnet-counters.
  • Vygenerujte soubor s výpisem paměti.
  • Analyzujte využití paměti pomocí souboru s výpisem paměti.

Předpoklady

Kurz používá:

Kurz předpokládá, že ukázkové aplikace a nástroje jsou nainstalované a připravené k použití.

Prozkoumání využití spravované paměti

Než začnete shromažďovat diagnostická data, která vám pomůžou tento scénář vyřešit, ujistěte se, že ve skutečnosti dochází k nevracení paměti (nárůst využití paměti). K potvrzení můžete použít nástroj dotnet-counters .

Otevřete okno konzoly a přejděte do adresáře, do kterého jste stáhli a rozbalil ukázkový cíl ladění. Spusťte cíl:

dotnet run

V samostatné konzole vyhledejte ID procesu:

dotnet-counters ps

Výstup by měl vypadat přibližně takto:

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

Teď pomocí nástroje dotnet-counters zkontrolujte využití spravované paměti. Určuje --refresh-interval počet sekund mezi aktualizacemi:

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

Živý výstup by měl vypadat přibližně takto:

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

Zaměření na tento řádek:

    GC Heap Size (MB)                                  4

Můžete vidět, že spravovaná paměť haldy je 4 MB hned po spuštění.

Teď přejděte na adresu URL https://localhost:5001/api/diagscenario/memleak/20000.

Všimněte si, že využití paměti se zvýšilo na 30 MB.

    GC Heap Size (MB)                                 30

Sledováním využití paměti můžete bezpečně říci, že paměť roste nebo nevracejí. Dalším krokem je shromáždit správná data pro analýzu paměti.

Generování výpisu paměti

Při analýze možných nevracení paměti potřebujete přístup k haldě paměti aplikace, abyste mohli analyzovat obsah paměti. Když se podíváte na vztahy mezi objekty, vytvoříte teorie o tom, proč není paměť uvolněna. Běžným zdrojem diagnostických dat je výpis paměti ve Windows nebo ekvivalentní výpis paměti v Linuxu. K vygenerování výpisu paměti aplikace .NET můžete použít nástroj dotnet-dump .

Spuštěním následujícího příkazu pomocí ukázkového cíle ladění vygenerujte výpis jádra Linuxu:

dotnet-dump collect -p 4807

Výsledkem je výpis paměti jádra umístěný ve stejné složce.

Writing minidump with heap to ./core_20190430_185145
Complete

Poznámka:

Pro porovnání v průběhu času nechte původní proces běžet i po shromáždění prvního výpisu a stejným způsobem shromážděte druhý výpis. Pak byste během časového období měli dva výpisy paměti, které můžete porovnat a zjistit, kde se využití paměti zvětšuje.

Restartování neúspěšného procesu

Po shromáždění výpisu paměti byste měli mít dostatek informací k diagnostice neúspěšného procesu. Pokud je proces selhání spuštěný na produkčním serveru, je teď ideální doba pro krátkodobou nápravu restartováním procesu.

V tomto kurzu jste teď hotovi s cílem ukázkového ladění a můžete ho zavřít. Přejděte do terminálu, který spustil server, a stiskněte Kombinaci kláves Ctrl+C.

Analýza výpisu paměti jádra

Teď, když máte vygenerovaný výpis paměti jádra, použijte nástroj dotnet-dump k analýze výpisu paměti:

dotnet-dump analyze core_20190430_185145

Kde core_20190430_185145 je název výpisu jádra, který chcete analyzovat.

Poznámka:

Pokud se zobrazí chyba s upozorněním, že libdl.so nelze najít, možná budete muset nainstalovat balíček libc6-dev . Další informace najdete v tématu Požadavky pro .NET v Linuxu.

Zobrazí se výzva, kde můžete zadat příkazy SOS . První věc, na kterou se chcete podívat, je obvykle celkový stav spravované haldy:

> 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

Tady vidíte, že většina objektů je buď String nebo Customer objektů.

Příkaz můžete znovu použít dumpheap s tabulkou metod (MT), abyste získali seznam všech String instancí:

> 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

Teď můžete pomocí gcroot příkazu v System.String instanci zjistit, jak a proč je objekt root:

> 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.

Vidíte, že String objekt je přímo uchovávaný objektem Customer a nepřímo ho CustomerCache drží objekt.

Můžete pokračovat v odhazování objektů, abyste viděli, že většina String objektů dodržuje podobný vzor. V tomto okamžiku šetření poskytlo dostatečné informace k identifikaci původní příčiny v kódu.

Tento obecný postup umožňuje identifikovat zdroj hlavních nevracení paměti.

Vyčištění prostředků

V tomto kurzu jste spustili ukázkový webový server. Tento server by měl být vypnutý, jak je vysvětleno v části Restartování neúspěšného procesu .

Můžete také odstranit vytvořený soubor s výpisem paměti.

Viz také

Další kroky