Eseguire il debug di una perdita di memoria in .NET
Questo articolo si applica a: ✔️ .NET Core 3.1 SDK e versioni successive
La memoria può subire una perdita quando l'applicazione fa riferimento a oggetti di cui non ha più bisogno per svolgere l'attività desiderata. Facendo riferimento a questi oggetti si impedisce al Garbage Collector di recuperare la memoria usata. Ciò può comportare una riduzione delle prestazioni e las generazione di un'eccezione OutOfMemoryException generata.
Questa esercitazione illustra gli strumenti per analizzare una perdita di memoria in un'app .NET usando gli strumenti dell'interfaccia della riga di comando di diagnostica .NET. Se si usa Windows, è possibile usare gli strumenti di diagnostica della memoria di Visual Studio per eseguire il debug della perdita di memoria.
Questa esercitazione usa un'app di esempio che subisce intenzionalmente una perdita di memoria come esercizio. È anche possibile analizzare le app che causano involontariamente perdite di memoria.
Questa esercitazione illustra come:
- Esaminare l'utilizzo della memoria gestita con dotnet-counters.
- Generare un file dump.
- Analizzare l'utilizzo della memoria usando il file dump.
Prerequisiti
L'esercitazione usa:
- .NET Core 3.1 SDK o versione successiva.
- dotnet-counters per controllare l'utilizzo della memoria gestita.
- dotnet-dump per raccogliere e analizzare un file dump (include l'estensione di debug SOS).
- Un'app di destinazione di debug di esempio da diagnosticare.
L'esercitazione presuppone che le app e gli strumenti di esempio siano installati e pronti per l'uso.
Esaminare l'utilizzo della memoria gestita
Prima di iniziare a raccogliere i dati di diagnostica per contribuire alla causa radice di questo scenario, assicurarsi di visualizzare effettivamente una perdita di memoria (aumento dell'utilizzo della memoria). È possibile usare lo strumento dotnet-counters per confermarlo.
Aprire una finestra della console e passare alla directory in cui è stato scaricato e decompresso la destinazione di debug di esempio. Eseguire la destinazione:
dotnet run
Da una console separata trovare l'ID processo:
dotnet-counters ps
L'output sarà simile al seguente:
4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios
Controllare ora l'utilizzo della memoria gestita con lo strumento dotnet-counters. --refresh-interval
specifica il numero di secondi tra gli aggiornamenti:
dotnet-counters monitor --refresh-interval 1 -p 4807
L'output live sarà simile al seguente:
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
Concentrarsi su questa riga:
GC Heap Size (MB) 4
È possibile notare che la memoria dell'heap gestita è di 4 MB subito dopo l'avvio.
Passare ora all'URL https://localhost:5001/api/diagscenario/memleak/20000
.
Osservare che l'utilizzo della memoria è cresciuto a 30 MB.
GC Heap Size (MB) 30
Osservando l'utilizzo della memoria, è possibile dire in modo sicuro che la perdita di memoria è in fase di crescita o perdita. Il passaggio successivo consiste nel raccogliere i dati corretti per l'analisi della memoria.
Generare il dump della memoria
Quando si analizzano possibili perdite di memoria, è necessario accedere all'heap di memoria dell'app per analizzare il contenuto della memoria. Esaminando le relazioni tra oggetti, si creano teorie sul motivo per cui la memoria non viene liberata. Un'origine dati di diagnostica comune è un dump della memoria in Windows o il dump core equivalente in Linux. Per generare un dump di un'applicazione .NET, è possibile usare lo strumento dotnet-dump.
Usando la destinazione di debug di esempio avviata in precedenza, eseguire il comando seguente per generare un dump di base di Linux:
dotnet-dump collect -p 4807
Il risultato è un dump principale che si trova nella stessa cartella.
Writing minidump with heap to ./core_20190430_185145
Complete
Nota
Per un confronto nel tempo, lasciare che il processo originale continui a essere in esecuzione dopo aver raccolto il primo dump e raccogliere un secondo dump nello stesso modo. Si avranno quindi due dump in un periodo di tempo che è possibile confrontare per vedere dove cresce l'utilizzo della memoria.
Riavviare il processo non riuscito
Dopo aver raccolto il dump, è necessario disporre di informazioni sufficienti per diagnosticare il processo non riuscito. Se il processo non riuscito è in esecuzione in un server di produzione, è il momento ideale per la correzione a breve termine riavviando il processo.
In questa esercitazione è stata completata la destinazione di debug di esempio ed è possibile chiuderla. Passare al terminale che ha avviato il server e premere CTRL+C.
Analizzare il dump principale
Ora che è stato generato un dump principale, usare lo strumento dotnet-dump per analizzare il dump:
dotnet-dump analyze core_20190430_185145
Dove core_20190430_185145
è il nome del dump principale da analizzare.
Nota
Se viene visualizzato un errore che segnala che non è possibile trovare libdl.so, potrebbe essere necessario installare il pacchetto libc6-dev. Per altre informazioni, vedere Prerequisiti per .NET in Linux.
Verrà visualizzato un prompt in cui è possibile immettere comandi SOS. In genere, la prima cosa da esaminare è lo stato complessivo dell'heap gestito:
> 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
Qui è possibile vedere che la maggior parte degli oggetti è String
o Customer
oggetti.
È possibile usare di nuovo il comando dumpheap
con la tabella del metodo (MT) per ottenere un elenco di tutte le istanze di 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
È ora possibile usare il gcroot
comando in un'istanzaSystem.String
per vedere come e perché l'oggetto è rooted:
> 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.
È possibile notare che String
è tenuto direttamente dall'oggetto Customer
e indirettamente mantenuto da un oggettoCustomerCache
.
È possibile continuare a eseguire il dump degli oggetti per verificare che la maggior parte degli oggetti String
segua un modello simile. A questo punto, l'indagine ha fornito informazioni sufficienti per identificare la causa radice nel codice.
Questa procedura generale consente di identificare l'origine di perdite di memoria principali.
Pulire le risorse
In questa esercitazione è stato avviato un server Web di esempio. Questo server dovrebbe essere stato arrestato come illustrato nella sezione Riavviare il processo non riuscito.
È anche possibile eliminare il file dump creato.
Vedi anche
- dotnet-trace per elencare i processi
- dotnet-counters per controllare l'utilizzo della memoria gestita
- dotnet-dump per raccogliere e analizzare un file dump
- dotnet/diagnostica
- Usare Visual Studio per eseguire il debug delle perdite di memoria