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:

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

Pasos siguientes