Depuración de una pérdida de memoria en .NET
Este artículo se aplica a: ✔️ SDK de .NET Core 3.1 y versiones posteriores
La memoria se puede perder cuando la aplicación hace referencia a objetos que ya no necesita para realizar la tarea deseada. Hacer referencia a estos objetos impide que el recolector de elementos no utilizados recupere la memoria usada. Esto puede provocar una degradación del rendimiento y una excepción OutOfMemoryException que se produce.
En este tutorial se muestran las herramientas para analizar una pérdida de memoria en una aplicación de .NET mediante las herramientas de la CLI de diagnóstico de .NET. Si está en Windows, puede usar las herramientas de diagnóstico de memoria de Visual Studio para depurar la pérdida de memoria.
En este tutorial se usa una aplicación de ejemplo que filtra intencionadamente la memoria, como ejercicio. También puede analizar aplicaciones que pierden memoria involuntariamente.
En este tutorial va a:
- Examinar el uso de memoria administrada con dotnet-counters.
- Generar un archivo de volcado de memoria.
- Analizar el uso de memoria mediante el archivo de volcado de memoria.
Requisitos previos
En el tutorial se usa:
- SDK de .NET Core 3.1 o una versión posterior
- dotnet-counters para comprobar el uso de memoria administrada.
- dotnet-dump para recopilar y analizar un archivo de volcado de memoria (incluye la extensión de depuración de SOS).
- Una aplicación de destino de depuración de ejemplo que se va a diagnosticar.
En el tutorial se da por supuesto que las herramientas y aplicaciones de ejemplo están instaladas y listas para usarse.
Análisis del uso de memoria administrada
Antes de empezar a recopilar datos de diagnóstico que ayuden a establecer la causa principal de este escenario, debe asegurarse de que realmente está viendo una fuga de memoria (crecimiento en el uso de memoria). Puede usar la herramienta dotnet-counters para confirmar eso.
Abra una ventana de consola y vaya al directorio donde descargó y descomprimió el destino de depuración de ejemplo. Ejecute el destino:
dotnet run
En una consola independiente, busque el id. de proceso:
dotnet-counters ps
La salida debe ser similar a:
4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios
Ahora, compruebe el uso de memoria administrada con la herramienta dotnet-counters. --refresh-interval
especifica el número de segundos entre las actualizaciones:
dotnet-counters monitor --refresh-interval 1 -p 4807
La salida en directo debe ser similar a:
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
Establecimiento del foco en esta línea:
GC Heap Size (MB) 4
Puede ver que la memoria de montón administrado es de 4 MB justo después del inicio.
Ahora, vaya a la dirección URL https://localhost:5001/api/diagscenario/memleak/20000
.
Observe que el uso de memoria ha aumentado a 30 MB.
GC Heap Size (MB) 30
Al observar el uso de memoria, puede indicar con seguridad el aumento o la fuga de memoria. El siguiente paso consiste en recopilar los datos adecuados para el análisis de memoria.
Generación de un volcado de memoria
Al analizar posibles fugas de memoria, debe acceder al montón de memoria de la aplicación para analizar el contenido de esta. Al observarse las relaciones entre los objetos, se crean teorías de por qué no se libera la memoria. Un origen de datos de diagnóstico habitual es un volcado de memoria en Windows o el volcado de memoria principal equivalente en Linux. Para generar un volcado de memoria de una aplicación .NET Core, puede usar la herramienta dotnet-dump.
Con el destino de depuración de ejemplo iniciado anteriormente, ejecute el siguiente comando para generar un volcado de memoria principal de Linux:
dotnet-dump collect -p 4807
El resultado es un volcado de memoria principal ubicado en la misma carpeta.
Writing minidump with heap to ./core_20190430_185145
Complete
Nota
Para obtener una comparación a lo largo del tiempo, deje que el proceso original continúe ejecutándose después de recopilar el primer volcado y recopile un segundo volcado de la misma manera. Como resultado de esto, tendría dos volcados de memoria durante un periodo de tiempo que puede comparar para ver dónde está creciendo el uso de memoria.
Reinicio del proceso con errores
Una vez recopilado el volcado de memoria, debe tener suficiente información para diagnosticar el proceso con errores. Si el proceso con errores se ejecuta en un servidor de producción, ahora es el momento ideal para la corrección a corto plazo reiniciando el proceso.
En este tutorial, ya ha terminado con el destino de depuración de ejemplo y puede cerrarlo. Vaya al terminal que inició el servidor y presione CTRL+C.
Análisis del volcado de memoria principal
Ahora que ha generado un volcado de memoria principal, use la herramienta dotnet-dump para analizar el volcado de memoria:
dotnet-dump analyze core_20190430_185145
Donde core_20190430_185145
es el nombre del volcado de memoria principal que desea analizar.
Nota:
Si ve un error que indica que no se encuentra libdl.so, es posible que tenga que instalar el paquete libc6-dev. Para obtener más información, consulte requisitos previos de para .NET en Linux.
Se le mostrará un mensaje donde puede escribir comandos de SOS . Normalmente, lo primero que desea ver es el estado general del montón administrado:
> 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
Aquí puede ver que la mayoría de los objetos son objetos String
o Customer
.
Puede volver a usar el comando dumpheap
con la tabla del método (MT) para obtener una lista de todas las instancias de 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
Ahora puede usar el comando gcroot
en una instancia de System.String
para ver cómo y por qué el objeto está rooteado:
> 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.
Puede ver que el objeto Customer
mantiene directamente String
y un objeto CustomerCache
lo hace indirectamente.
Puede seguir volcando objetos para ver que la mayoría de los objetos String
siguen un patrón similar. En este punto, la investigación proporcionó suficiente información para identificar la causa principal en su código.
Este procedimiento general le permite identificar el origen de las principales fugas de memoria.
Limpiar los recursos
En este tutorial, inició un servidor web de ejemplo. Este servidor debería haberse apagado como se explica en la sección Reinicio del proceso con errores.
También puede eliminar el archivo de volcado de memoria que se creó.
Consulte también
- dotnet-trace para mostrar procesos
- dotnet-counters para comprobar el uso de memoria administrada
- dotnet-dump para recopilar y analizar un archivo de volcado de memoria
- dotnet/diagnostics
- Uso de Visual Studio para depurar fugas de memoria