Fouten opsporen in een geheugenlek in .NET

Dit artikel is van toepassing op: ✔️ .NET Core 3.1 SDK en latere versies

Geheugen kan lekken wanneer uw app verwijst naar objecten die niet langer nodig zijn om de gewenste taak uit te voeren. Als u naar deze objecten verwijst, voorkomt u dat de garbagecollector het gebruikte geheugen vrijgeeft. Dit kan leiden tot prestatievermindering en een OutOfMemoryException uitzondering die wordt gegenereerd.

In deze zelfstudie ziet u de hulpprogramma's voor het analyseren van een geheugenlek in een .NET-app met behulp van de CLI-hulpprogramma's voor diagnostische .NET-gegevens. Als u windows gebruikt, kunt u mogelijk de hulpprogramma's voor geheugendiagnose van Visual Studio gebruiken om fouten in het geheugenlek op te sporen.

In deze zelfstudie wordt een voorbeeld-app gebruikt die opzettelijk geheugen lekt, als oefening. U kunt ook apps analyseren die onbedoeld geheugen lekken.

In deze zelfstudie leert u het volgende:

  • Bekijk het gebruik van beheerd geheugen met dotnet-tellers.
  • Genereer een dumpbestand.
  • Analyseer het geheugengebruik met behulp van het dumpbestand.

Vereisten

In de zelfstudie wordt het volgende gebruikt:

In de zelfstudie wordt ervan uitgegaan dat de voorbeeld-apps en -hulpprogramma's zijn geïnstalleerd en klaar zijn voor gebruik.

Beheerd geheugengebruik onderzoeken

Voordat u begint met het verzamelen van diagnostische gegevens om de hoofdoorzaak van dit scenario te helpen, moet u ervoor zorgen dat u daadwerkelijk een geheugenlek ziet (groei in geheugengebruik). U kunt het hulpprogramma dotnet-tellers gebruiken om dat te bevestigen.

Open een consolevenster en navigeer naar de map waarin u het voorbeeld van het foutopsporingsdoel hebt gedownload en uitgepakt. Voer het doel uit:

dotnet run

Zoek in een afzonderlijke console de proces-id:

dotnet-counters ps

De uitvoer moet er ongeveer als volgt uitzien:

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

Controleer nu het beheerde geheugengebruik met het hulpprogramma dotnet-tellers . Hiermee --refresh-interval geeft u het aantal seconden tussen vernieuwingen op:

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

De live-uitvoer moet vergelijkbaar zijn met:

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

Focus op deze regel:

    GC Heap Size (MB)                                  4

U kunt zien dat het beheerde heap-geheugen na het opstarten 4 MB is.

Ga nu naar de URL https://localhost:5001/api/diagscenario/memleak/20000.

U ziet dat het geheugengebruik is gegroeid tot 30 MB.

    GC Heap Size (MB)                                 30

Door het geheugengebruik te bekijken, kunt u veilig zeggen dat het geheugen groeit of lekt. De volgende stap is het verzamelen van de juiste gegevens voor geheugenanalyse.

Geheugendump genereren

Wanneer u mogelijke geheugenlekken analyseert, hebt u toegang nodig tot de heap geheugen van de app om de inhoud van het geheugen te analyseren. Als u relaties tussen objecten bekijkt, maakt u theorieën over waarom geheugen niet wordt vrijgemaakt. Een algemene diagnostische gegevensbron is een geheugendump in Windows of de equivalente kerndump op Linux. Als u een dump van een .NET-toepassing wilt genereren, kunt u het hulpprogramma dotnet-dump gebruiken.

Voer de volgende opdracht uit om een Linux-kerndump te genereren met behulp van het eerder gestarte voorbeeld van foutopsporingsdoel :

dotnet-dump collect -p 4807

Het resultaat is een kerndump die zich in dezelfde map bevindt.

Writing minidump with heap to ./core_20190430_185145
Complete

Notitie

Voor een vergelijking in de loop van de tijd kunt u het oorspronkelijke proces blijven uitvoeren na het verzamelen van de eerste dump en een tweede dump op dezelfde manier verzamelen. Vervolgens hebt u gedurende een bepaalde periode twee dumps die u kunt vergelijken om te zien waar het geheugengebruik groeit.

Het mislukte proces opnieuw starten

Zodra de dump is verzameld, moet u voldoende informatie hebben om het mislukte proces te diagnosticeren. Als het mislukte proces wordt uitgevoerd op een productieserver, is het nu de ideale tijd voor herstel op korte termijn door het proces opnieuw te starten.

In deze zelfstudie bent u nu klaar met het voorbeeld van foutopsporingsdoel en kunt u deze sluiten. Navigeer naar de terminal waarmee de server is gestart en druk op Ctrl+C.

De kerndump analyseren

Nu u een kerndump hebt gegenereerd, gebruikt u het hulpprogramma dotnet-dump om de dump te analyseren:

dotnet-dump analyze core_20190430_185145

Waar core_20190430_185145 is de naam van de kerndump die u wilt analyseren.

Notitie

Als er een foutbericht wordt weergegeven dat libdl.so niet kan worden gevonden, moet u mogelijk het libc6-dev-pakket installeren. Zie Vereisten voor .NET in Linux voor meer informatie.

U krijgt een prompt te zien waarin u SOS-opdrachten kunt invoeren. Meestal is het eerste wat u wilt bekijken de algehele status van de beheerde 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

Hier ziet u dat de meeste objecten ofwel objecten zijn String of Customer objecten.

U kunt de dumpheap opdracht opnieuw gebruiken met de methodetabel (MT) om een lijst met alle String exemplaren op te halen:

> 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

U kunt nu de gcroot opdracht op een System.String exemplaar gebruiken om te zien hoe en waarom het object is geroot:

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

U kunt zien dat het String rechtstreeks wordt vastgehouden door het Customer object en indirect wordt vastgehouden door een CustomerCache object.

U kunt objecten blijven dumpen om te zien dat de meeste String objecten een vergelijkbaar patroon volgen. Op dit moment heeft het onderzoek voldoende informatie verstrekt om de hoofdoorzaak in uw code te identificeren.

Met deze algemene procedure kunt u de bron van belangrijke geheugenlekken identificeren.

Resources opschonen

In deze zelfstudie hebt u een voorbeeldwebserver gestart. Deze server moet zijn afgesloten, zoals wordt uitgelegd in de sectie Opnieuw opstarten van het mislukte proces .

U kunt ook het dumpbestand verwijderen dat is gemaakt.

Zie ook

Volgende stappen