Compartir a través de


Recolección de basura y rendimiento

En este artículo se describen los problemas relacionados con la recolección de elementos no utilizados y el uso de memoria. Aborda problemas relacionados con el montón administrado y explica cómo minimizar el efecto de la recolección de basura en sus aplicaciones. Cada número tiene enlaces a procedimientos que puede usar para investigar inconvenientes.

Herramientas de análisis de rendimiento

En las secciones siguientes se describen las herramientas disponibles para investigar problemas de uso de memoria y recolección de elementos no utilizados. Los procedimientos que se proporcionan más adelante en este artículo hacen referencia a estas herramientas.

Contadores de rendimiento de memoria

Puede usar contadores de rendimiento para recopilar datos de rendimiento. Para obtener instrucciones, consulte Generación de perfiles en tiempo de ejecución. La categoría CLR Memory de .NET de los contadores de rendimiento, como se describe en Contadores de rendimiento en .NET Framework, ofrece información sobre el recolector de elementos no utilizados.

Depurar con SOS

Puede usar el depurador de Windows (WinDbg) para inspeccionar objetos del montón administrado.

Para instalar WinDbg, instale Herramientas de depuración para Windows desde la página Descargar herramientas de depuración para Windows .

Eventos ETW de recolección de elementos no utilizados

El seguimiento de eventos para Windows (ETW) es un sistema de seguimiento que complementa la compatibilidad de generación de perfiles y depuración proporcionada por .NET. A partir de .NET Framework 4, los eventos ETW de recolección de basura capturan información útil para analizar el montón administrado desde un punto de vista estadístico. Por ejemplo, el GCStart_V1 evento, que se genera cuando está a punto de ocurrir una recolección de basura, proporciona la siguiente información:

  • La generación de objetos que se recolecta.
  • Lo que desencadenó la recolección de basura.
  • Tipo de recolección de basura (concurrente o no concurrente).

El registro de eventos ETW es eficaz y no enmascarará ningún problema de rendimiento asociado a la recolección de basura. Un proceso puede proporcionar sus propios eventos junto con los eventos ETW. Cuando se registran, tanto los eventos de la aplicación como los eventos de la recolección de elementos no utilizados se pueden poner en correlación para determinar cómo y cuándo se producen problemas de pila. Por ejemplo, una aplicación de servidor podría proporcionar eventos al inicio y al final de una solicitud de cliente.

Api de generación de perfiles

Las interfaces de generación de perfiles de Common Language Runtime (CLR) proporcionan información detallada sobre los objetos afectados durante la recolección de elementos no utilizados. Un generador de perfiles puede recibir una notificación cuando se inicia y finaliza una recolección de elementos no utilizados. Puede proporcionar informes sobre los objetos del montón administrado, incluida una identificación de objetos en cada generación. Para obtener más información, consulte Introducción a la generación de perfiles.

Los generadores de perfiles pueden proporcionar información completa. Sin embargo, los generadores de perfiles complejos pueden modificar potencialmente el comportamiento de una aplicación.

Supervisión de recursos de dominio de aplicación

A partir de .NET Framework 4, la supervisión de recursos de dominio de aplicación (ARM) permite a los hosts supervisar el uso de CPU y memoria por dominio de aplicación. Para obtener más información, consulte Supervisión de recursos de dominio de aplicación.

Solución de problemas de rendimiento

El primer paso es determinar si el problema es realmente la recolección de elementos no utilizados. Si determina que es así, seleccione en la lista siguiente para solucionar el problema.

Problema: Se inicia una excepción de memoria insuficiente

Hay dos casos justificados para que se produzca una excepción OutOfMemoryException administrada:

  • Agotamiento de memoria virtual.

    El recolector de basura asigna memoria del sistema en segmentos de un tamaño predeterminado. Si una asignación requiere un segmento adicional, pero no queda ningún bloque libre contiguo en el espacio de memoria virtual del proceso, se producirá un error en la asignación del montón administrado.

  • No tener suficiente memoria física para asignar.

Comprobaciones de rendimiento
Determine si se gestiona la excepción de memoria insuficiente.
Determine la cantidad de memoria virtual que se puede reservar.
Determine si hay suficiente memoria física.

Si determina que la excepción no es legítima, póngase en contacto con el Servicio al cliente de Microsoft y soporte técnico con la siguiente información:

  • La pila con la excepción administrada de memoria insuficiente.
  • Un volcado de memoria completo.
  • Datos que demuestran que no es una excepción legítima de memoria insuficiente, incluidos los datos que muestran que la memoria virtual o física no es un problema.

Problema: el proceso usa demasiada memoria

Una suposición común es que el uso de memoria se muestra en la pestaña Rendimiento del Administrador de tareas de Windows puede indicar cuándo se usa demasiada memoria. Sin embargo, esa pantalla pertenece al conjunto de trabajo; no proporciona información sobre el uso de memoria virtual.

Si determina que el problema está provocado por el montón administrado, debe medir el montón administrado a lo largo del tiempo para determinar cualquier patrón.

Si determina que el problema no está provocado por el montón administrado, debe usar la depuración nativa.

Comprobaciones de rendimiento
Determine la cantidad de memoria virtual que se puede reservar.
Determine cuánta memoria está confirmando el montón administrado.
Determine cuánta memoria reserva el montón administrado.
Determine objetos grandes en la generación 2.
Determine las referencias a objetos.

Problema: El Recolector de Basura no recupera objetos lo suficientemente rápido

Cuando parezca que los objetos no se recuperan según lo previsto para la recolección de basura, debe determinar si hay referencias fuertes a esos objetos.

También puede encontrar este problema si no se ha producido ninguna recolección de elementos no utilizados para la generación que contiene un objeto muerto, lo que indica que el finalizador del objeto muerto no se ha ejecutado. Por ejemplo, esto puede ocurrir cuando se ejecuta una aplicación de contenedor uniproceso (STA) y el subproceso que atiende a la cola del finalizador no la puede llamar.

Comprobaciones de rendimiento
Compruebe las referencias a objetos.
Determine si se ha ejecutado un finalizador.
Determine si hay objetos a la espera de finalizar.

Problema: El montón administrado está demasiado fragmentado

El nivel de fragmentación se calcula como la proporción de espacio libre en la memoria total asignada para la generación. Para la generación 2, un nivel aceptable de fragmentación no es superior a 20%. Dado que la generación 2 puede ser muy grande, la proporción de fragmentación es más importante que el valor absoluto.

Tener un montón de espacio libre en la generación 0 no es un problema porque se trata de la generación donde se asignan nuevos objetos.

La fragmentación siempre se produce en el montón de objetos grandes porque no está compactado. Los objetos libres adyacentes se contraen naturalmente en un único espacio para satisfacer las solicitudes de asignación de objetos grandes.

La fragmentación puede convertirse en un problema en la generación 1 y la generación 2. Si estas generaciones tienen una gran cantidad de espacio libre después de una recolección de elementos no utilizados, el uso de objetos de una aplicación puede necesitar modificaciones y debe considerar la posibilidad de volver a evaluar la duración de los objetos a largo plazo.

El anclaje excesivo de objetos puede aumentar la fragmentación. Si la fragmentación es elevada, puede que haya demasiados objetos anclados.

Si la fragmentación de la memoria virtual impide que el recolector de elementos no utilizados agregue segmentos, las causas podrían ser una de las siguientes:

  • Carga y descarga frecuentes de muchos ensamblados pequeños.

  • Mantener demasiadas referencias a objetos COM al interoperar con código no administrado.

  • Creación de objetos grandes transitorios, lo que hace que el montón de objetos grandes asigne y libere segmentos del montón con frecuencia.

    Al hospedar el CLR, una aplicación puede solicitar que el recolector de elementos no utilizados conserve sus segmentos. Esto reduce la frecuencia de las asignaciones de segmentos. Para ello se emplea la marca STARTUP_HOARD_GC_VM en la enumeración STARTUP_FLAGS.

Comprobaciones de rendimiento
Determine la cantidad de espacio libre en el montón administrado.
Determine el número de objetos anclados.

Si cree que no hay ninguna causa legítima para la fragmentación, póngase en contacto con el Servicio al cliente y el soporte técnico de Microsoft.

Problema: las pausas de recolección de basura son demasiado largas

El recolector de basura opera en tiempo real flexible, por lo que una aplicación debe ser capaz de tolerar ciertas pausas. Un criterio para el tiempo real flexible es que el 95% de las operaciones debe finalizar a tiempo.

En la recolección de elementos no utilizados simultánea, se permite la ejecución de subprocesos administrados durante una recolección, lo que significa que las pausas son mínimas.

Las recolecciones efímeras de basura (generaciones 0 y 1) duran solo unos pocos milisegundos, por lo que la reducción de pausas normalmente no es factible. Sin embargo, puede reducir las pausas en las recolecciones de la generación 2 cambiando el modelo de las solicitudes de asignación de una aplicación.

Otro método, más preciso, consiste en usar eventos ETW de recolección de basura. Puede averiguar los controles de tiempo para las recolecciones si suma las diferencias de marca de tiempo para una secuencia de eventos. La secuencia de recolección completa incluye la suspensión del motor de ejecución, la recolección de elementos no utilizados propiamente dicha y la reanudación del motor de ejecución.

Puede usar Notificaciones de recolección de basura para determinar si un servidor está a punto de tener una recolección de la generación 2 y si la reenrutación de solicitudes a otro servidor podría aliviar cualquier problema con las pausas.

Comprobaciones de rendimiento
Determine la duración de una recolección de elementos no utilizados.
Determine lo que causó una recolección de basura.

Problema: La generación 0 es demasiado grande

Es probable que la generación 0 tenga un mayor número de objetos en un sistema de 64 bits, especialmente cuando se usa la recolección de elementos no utilizados del servidor en lugar de la recolección de elementos no utilizados de estación de trabajo. Esto se debe a que el umbral para desencadenar una recolección de elementos no utilizados de generación 0 es mayor en estos entornos y las recolecciones de generación 0 pueden aumentar mucho más. El rendimiento se mejora cuando una aplicación asigna más memoria antes de que se desencadene una recolección de elementos no utilizados.

Problema: el uso de CPU durante una recolección de elementos no utilizados es demasiado alto

El uso de la CPU será elevado durante una recolección de elementos no utilizados. Si se dedica una cantidad significativa de tiempo de proceso en una recolección de basura, el número de recolecciones es demasiado frecuente o las recolecciones duran demasiado. Una proporción de asignación de objetos mayor en el montón administrado hace que la recolección de elementos no utilizados se realice con más frecuencia. Al reducir la tasa de asignación, se reduce la frecuencia de las recolecciones de basura.

Puede supervisar las tasas de asignación mediante el contador de rendimiento Allocated Bytes/second. Para obtener más información, vea Contadores de rendimiento en .NET.

La duración de una colección es principalmente un factor del número de objetos que sobreviven después de la asignación. El recolector de basura debe pasar por una gran cantidad de memoria si muchos objetos quedan por recolectar. El trabajo para coordinar a los sobrevivientes requiere mucho tiempo. Para determinar cuántos objetos fueron manejados durante una recolección, establezca un punto de interrupción en el depurador al final de una recolección de basura para una generación especificada.

Comprobaciones de rendimiento
Determine si el uso elevado de la CPU se debe a la recolección de elementos no utilizados.
Coloca un punto de quiebre al final de la recolección de basura.

Instrucciones para la solución de problemas

En esta sección se describen las directrices que debe tener en cuenta a medida que comience las investigaciones.

Recolección de elementos no utilizados de estación de trabajo o servidor

Determine si está utilizando el tipo correcto de recolección de basura. Si tu aplicación usa varios subprocesos e instancias de objeto, utiliza la recolección de basura del servidor en lugar de la recolección de basura de estación de trabajo. La recolección de elementos no utilizados del servidor funciona en varios subprocesos, mientras que la recolección de elementos no utilizados de estación de trabajo requiere varias instancias de una aplicación para ejecutar sus propios subprocesos de recolección de elementos no utilizados y competir por el tiempo de CPU.

Una aplicación que tiene una carga baja y que realiza tareas con poca frecuencia en segundo plano, como un servicio, podría usar la recolección de basura de estación de trabajo con la recolección de basura concurrente deshabilitada.

Cuándo medir el tamaño del montón administrado

A menos que use un generador de perfiles, tendrá que establecer un patrón de medición coherente para diagnosticar de forma eficaz los problemas de rendimiento. Tenga en cuenta los siguientes puntos para establecer una programación:

  • Si mide después de una recolección de elementos no utilizados de generación 2, todo el montón administrado estará libre de elementos no utilizados (objetos muertos).
  • Si mide inmediatamente después de una recolección de elementos no utilizados de generación 0, los objetos de las generaciones 1 y 2 no se recolectarán todavía.
  • Si mide inmediatamente antes de una recolección de elementos no utilizados, se medirá toda la asignación posible antes de que comience dicha recolección.
  • La medición durante una recolección de elementos no utilizados es problemática, ya que las estructuras de datos del recolector de elementos no utilizados no están en un estado válido para el recorrido y es posible que no puedan proporcionarle los resultados completos. es así por diseño.
  • Cuando se usa la recolección de elementos no utilizados de estación de trabajo con la recolección simultánea de elementos no utilizados, los objetos recuperados no se compactan, por lo que el tamaño del montón puede ser igual o mayor (la fragmentación puede hacer que parezca mayor).
  • La recolección de basura concurrente en la generación 2 se retrasa cuando la carga de la memoria física es demasiado alta.

En el procedimiento siguiente se describe cómo establecer un punto de interrupción para que pueda medir el montón administrado.

Para establecer un punto de interrupción al final de la recolección de basura

  • En WinDbg con la extensión del depurador de SOS cargada, escriba el siguiente comando:

    bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"

    Establecer GcCondemnedGeneration en la generación deseada. Este comando requiere símbolos privados.

    Este comando fuerza una interrupción si RestartEE se ejecuta después de que los objetos de generación 2 hayan sido recuperados para la recolección de basura.

    En la recolección de elementos no utilizados del servidor, solo un subproceso llama a RestartEE, por lo que el punto de interrupción solo se producirá una vez durante una recolección de elementos no utilizados de generación 2.

Procedimientos de comprobación de rendimiento

En esta sección se describen los procedimientos siguientes para aislar la causa del problema de rendimiento:

Para determinar si el problema se debe a la recolección de basura

  • Examine los dos contadores de rendimiento de memoria siguientes:

    • % de tiempo de GC. Muestra el porcentaje de tiempo transcurrido que se ha dedicado a realizar una recolección de basura después del último ciclo de recolección. Use este contador para determinar si el recolector de elementos no utilizados está dedicando demasiado tiempo a hacer que haya espacio disponible en el montón administrado. Si el tiempo invertido en la recolección de basura es relativamente bajo, eso podría indicar un problema con los recursos fuera del montón administrado. Este contador puede no ser preciso cuando está implicada la recolección de basura simultánea o en segundo plano.

    • # Total de bytes comprometidos. Muestra la cantidad de memoria virtual comprometida actualmente por el recolector de basura. Use este contador para determinar si la memoria consumida por el recolector de elementos no utilizados es una parte excesiva de la memoria que usa la aplicación.

    La mayoría de los contadores de rendimiento de memoria se actualizan al final de cada recolección de basura. Por lo tanto, es posible que no reflejen las condiciones actuales sobre las que desea obtener información.

Para determinar si se gestiona la excepción de memoria insuficiente

  1. En el depurador de WinDbg o Visual Studio con la extensión del depurador SOS cargada, escriba el comando 'print exception' (pe):

    !pe

    Si se administra la excepción, OutOfMemoryException se muestra como el tipo de excepción, como se muestra en el ejemplo siguiente.

    Exception object: 39594518
    Exception type: System.OutOfMemoryException
    Message: <none>
    InnerException: <none>
    StackTrace (generated):
    
  2. Si la salida no especifica una excepción, debe determinar de qué subproceso procede la excepción fuera de memoria. Escriba el siguiente comando en el depurador para mostrar todos los subprocesos con sus pilas de llamadas:

    ~\*kb

    El argumento RaiseTheException indica el subproceso con la pila que tiene llamadas de excepción. Este es el objeto de excepción administrada.

    28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
    
  3. Puede usar el comando siguiente para volcar las excepciones anidadas.

    !pe -nested

    Si no encuentra ninguna excepción, la excepción de falta de memoria se originó en código no administrado.

Para determinar la cantidad de memoria virtual que se puede reservar

  • En WinDbg con la extensión del depurador de SOS cargada, escriba el siguiente comando para obtener la región libre más grande.

    !address -summary

    La región libre más grande se presenta como se muestra en la salida siguiente.

    Largest free region: Base 54000000 - Size 0003A980
    

    En este ejemplo, el tamaño de la región libre más grande es de aproximadamente 24000 KB (3A980 en hexadecimal). Esta región es mucho más pequeña que la que necesita el recolector de basura para un segmento.

    o

  • Use el comando vmstat:

    !vmstat

    La región libre más grande es el valor más grande de la columna MAXIMUM, como se muestra en la salida siguiente.

    TYPE        MINIMUM   MAXIMUM     AVERAGE   BLK COUNT   TOTAL
    ~~~~        ~~~~~~~   ~~~~~~~     ~~~~~~~   ~~~~~~~~~~  ~~~~
    Free:
    Small       8K        64K         46K       36          1,671K
    Medium      80K       864K        349K      3           1,047K
    Large       1,384K    1,278,848K  151,834K  12          1,822,015K
    Summary     8K        1,278,848K  35,779K   51          1,824,735K
    

Para determinar si hay suficiente memoria física

  1. Inicie el Administrador de tareas de Windows.

  2. En la pestaña Performance, observe el valor confirmado. (En Windows 7, mira el Commit (KB) en el System group.)

    Si el Total está cerca del Limit, tienes poca memoria física disponible.

Para determinar cuánta memoria está confirmando el montón administrado

  • Use el contador de rendimiento de memoria # Total committed bytes para obtener el número de bytes que el montón administrado está confirmando. El recolector de elementos no utilizados confirma fragmentos de un segmento a medida que son necesarios, no todos al mismo tiempo.

    Nota:

    No use el contador de rendimiento # Bytes in all Heaps, ya que no representa el uso de memoria real por parte del montón administrado. El tamaño de una generación se incluye en este valor y es realmente su tamaño de umbral, es decir, el tamaño que induce una recolección de basura si la generación se llena de objetos. Por lo tanto, este valor suele ser cero.

Para determinar cuánta memoria reserva el montón administrado

  • Use el # Total reserved bytes contador de rendimiento de memoria.

    El recolector de elementos no utilizados reserva memoria en segmentos y puede determinar dónde comienza un segmento mediante el eeheap comando .

    Importante

    Aunque puede determinar la cantidad de memoria que asigna el recolector de elementos no utilizados para cada segmento, el tamaño del segmento es específico de la implementación y está sujeto a cambios en cualquier momento, incluido en actualizaciones periódicas. La aplicación nunca debe hacer suposiciones sobre o depender de un tamaño de segmento determinado, ni tampoco debe intentar configurar la cantidad de memoria disponible para las asignaciones de segmentos.

  • En el depurador de WinDbg o Visual Studio con la extensión del depurador SOS cargada, escriba el siguiente comando:

    !eeheap -gc

    El resultado es el siguiente.

    Number of GC Heaps: 2
    ------------------------------
    Heap 0 (002db550)
    generation 0 starts at 0x02abe29c
    generation 1 starts at 0x02abdd08
    generation 2 starts at 0x02ab0038
    ephemeral segment allocation context: none
      segment    begin allocated     size
    02ab0000 02ab0038  02aceff4 0x0001efbc(126908)
    Large object heap starts at 0x0aab0038
      segment    begin allocated     size
    0aab0000 0aab0038  0aab2278 0x00002240(8768)
    Heap Size   0x211fc(135676)
    ------------------------------
    Heap 1 (002dc958)
    generation 0 starts at 0x06ab1bd8
    generation 1 starts at 0x06ab1bcc
    generation 2 starts at 0x06ab0038
    ephemeral segment allocation context: none
      segment    begin allocated     size
    06ab0000 06ab0038  06ab3be4 0x00003bac(15276)
    Large object heap starts at 0x0cab0038
      segment    begin allocated     size
    0cab0000 0cab0038  0cab0048 0x00000010(16)
    Heap Size    0x3bbc(15292)
    ------------------------------
    GC Heap Size   0x24db8(150968)
    

    Las direcciones indicadas por "segmento" son las direcciones iniciales de los segmentos.

Para determinar objetos grandes en la generación 2

  • En el depurador de WinDbg o Visual Studio con la extensión del depurador SOS cargada, escriba el siguiente comando:

    !dumpheap –stat

    Si el montón administrado es grande, dumpheap puede tardar bastante tiempo en finalizar.

    Puede empezar a analizar desde las últimas líneas de la salida, ya que enumeran los objetos que usan más espacio. Por ejemplo:

    2c6108d4   173712     14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo
    00155f80      533     15216804      Free
    7a747c78   791070     15821400 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700930     19626040 System.Collections.Specialized.ListDictionary
    2c64e36c    78644     20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo
    79124228   121143     29064120 System.Object[]
    035f0ee4    81626     35588936 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    791242ec    40182     90664128 System.Collections.Hashtable+bucket[]
    790fa3e0  3154024    137881448 System.String
    Total 8454945 objects
    

    El último objeto enumerado es una cadena y ocupa el máximo espacio. Puede examinar la aplicación para ver cómo se pueden optimizar los objetos de cadena. Para ver las cadenas que están entre 150 y 200 bytes, escriba lo siguiente:

    !dumpheap -type System.String -min 150 -max 200

    Un ejemplo de los resultados es el siguiente.

    Address  MT           Size  Gen
    1875d2c0 790fa3e0      152    2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11
    …
    

    El uso de un entero en lugar de una cadena para un identificador puede ser más eficaz. Si la misma cadena se está repitiendo miles de veces, considere la posibilidad de asignación al grupo interno de cadenas. Para obtener más información sobre la asignación al grupo interno de cadenas, vea el tema de referencia sobre el método String.Intern.

Para determinar las referencias a objetos

  • En WinDbg con la extensión del depurador de SOS cargada, escriba el siguiente comando para enumerar las referencias a objetos:

    !gcroot

    o

  • Para determinar las referencias de un objeto específico, incluya la dirección :

    !gcroot 1c37b2ac

    Las raíces encontradas en pilas pueden ser falsos positivos. Para obtener más información, use el comando !help gcroot.

    ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)->
    19010b78(DemoApp.FormDemoApp)->
    19011158(System.Windows.Forms.PropertyStore)->
    … [omitted]
    1c3745ec(System.Data.DataTable)->
    1c3747a8(System.Data.DataColumnCollection)->
    1c3747f8(System.Collections.Hashtable)->
    1c376590(System.Collections.Hashtable+bucket[])->
    1c376c98(System.Data.DataColumn)->
    1c37b270(System.Data.Common.DoubleStorage)->
    1c37b2ac(System.Double[])
    Scan Thread 0 OSTHread 99c
    Scan Thread 6 OSTHread 484
    

    El gcroot comando puede tardar mucho tiempo en finalizar. Todo objeto no reclamado en la recolección de elementos no utilizados es un objeto activo. Esto significa que alguna raíz se mantiene directa o indirectamente en el objeto, por lo que gcroot debe devolver información de ruta de acceso al objeto. Debe examinar los gráficos devueltos y ver por qué todavía se hace referencia a estos objetos.

Para determinar si se ha ejecutado un finalizador

  • Ejecute un programa de prueba que contenga el código siguiente:

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    

    Si la prueba resuelve el problema, significa que el recolector de elementos no utilizados no estaba recuperando objetos porque los finalizadores de esos objetos se habían suspendido. El GC.WaitForPendingFinalizers método permite que los finalizadores completen sus tareas y corrija el problema.

Para determinar si hay objetos a la espera de ser finalizados

  1. En el depurador de WinDbg o Visual Studio con la extensión del depurador SOS cargada, escriba el siguiente comando:

    !finalizequeue

    Examine el número de objetos que están listos para finalizar. Si el número es elevado, debe examinar por qué estos finalizadores no pueden progresar en absoluto o con la rapidez suficiente.

  2. Para obtener una salida de subprocesos, escriba el siguiente comando:

    !threads -special

    Este comando proporciona una salida como la siguiente.

       OSID     Special thread type
    2    cd0    DbgHelper
    3    c18    Finalizer
    4    df0    GC SuspendEE
    

    El subproceso del finalizador indica qué finalizador se está ejecutando actualmente, si lo hay. Cuando un subproceso de finalizador no ejecuta ningún finalizador, está esperando que un evento le indique que realice su trabajo. La mayoría de las veces verá el subproceso finalizador en este estado porque se ejecuta en THREAD_HIGHEST_PRIORITY y se presupone que termina de ejecutar los finalizadores, si hay alguno, muy rápidamente.

Para determinar la cantidad de espacio disponible en el montón administrado

  • En el depurador de WinDbg o Visual Studio con la extensión del depurador SOS cargada, escriba el siguiente comando:

    !dumpheap -type Free -stat

    Este comando muestra el tamaño total de todos los objetos libres en el montón administrado, como se muestra en el ejemplo siguiente.

    total 230 objects
    Statistics:
          MT    Count    TotalSize Class Name
    00152b18      230     40958584      Free
    Total 230 objects
    
  • Para determinar el espacio libre en la generación 0, escriba el siguiente comando para obtener información de consumo de memoria por generación:

    !eeheap -gc

    Este comando muestra una salida similar a la siguiente. La última línea muestra el segmento efímero.

    Heap 0 (0015ad08)
    generation 0 starts at 0x49521f8c
    generation 1 starts at 0x494d7f64
    generation 2 starts at 0x007f0038
    ephemeral segment allocation context: none
    segment  begin     allocated  size
    00178250 7a80d84c  7a82f1cc   0x00021980(137600)
    00161918 78c50e40  78c7056c   0x0001f72c(128812)
    007f0000 007f0038  047eed28   0x03ffecf0(67103984)
    3a120000 3a120038  3a3e84f8   0x002c84c0(2917568)
    46120000 46120038  49e05d04   0x03ce5ccc(63855820)
    
  • Calcule el espacio utilizado por la generación 0:

    ? 49e05d04-0x49521f8c

    El resultado es el siguiente. La generación 0 es de aproximadamente 9 MB.

    Evaluate expression: 9321848 = 008e3d78
    
  • El comando siguiente vuelca el espacio disponible dentro del intervalo de la generación 0:

    !dumpheap -type Free -stat 0x49521f8c 49e05d04

    El resultado es el siguiente.

    ------------------------------
    Heap 0
    total 409 objects
    ------------------------------
    Heap 1
    total 0 objects
    ------------------------------
    Heap 2
    total 0 objects
    ------------------------------
    Heap 3
    total 0 objects
    ------------------------------
    total 409 objects
    Statistics:
          MT    Count TotalSize Class Name
    0015a498      409   7296540      Free
    Total 409 objects
    

    Esta salida muestra que la parte de generación 0 del montón usa 9 MB de espacio para los objetos y tiene 7 MB libres. Este análisis muestra la medida en la que la generación 0 contribuye a la fragmentación. Esta cantidad de uso del montón se debe descontar de la cantidad total como la causa de fragmentación por parte de los objetos de larga duración.

Para determinar el número de objetos anclados

  • En el depurador de WinDbg o Visual Studio con la extensión del depurador SOS cargada, escriba el siguiente comando:

    !gchandles

    Las estadísticas mostradas incluyen el número de identificadores anclados, como se muestra en el ejemplo siguiente.

    GC Handle Statistics:
    Strong Handles:      29
    Pinned Handles:      10
    

Para determinar la duración de una recolección de elementos no utilizados

  • Examine el contador de rendimiento de % Time in GC memoria.

    El valor se calcula mediante un tiempo de intervalo de ejemplo. Dado que los contadores se actualizan al final de cada recolección de elementos no utilizados, la muestra actual tendrá el mismo valor que el ejemplo anterior si no se produjeron recopilaciones durante el intervalo.

    El tiempo de recopilación se obtiene multiplicando el tiempo de intervalo de muestra con el valor de porcentaje.

    Los datos siguientes muestran cuatro intervalos de muestreo de dos segundos, para un estudio de 8 segundos. Las Gen0, Gen1, y Gen2 columnas muestran el número total de recolecciones de basura que se han completado al final del intervalo para esa generación.

    Interval    Gen0    Gen1    Gen2    % Time in GC
            1       9       3       1              10
            2      10       3       1               1
            3      11       3       1               3
            4      11       3       1               3
    

    Esta información no muestra cuándo se produjo la recolección de elementos no utilizados, pero puede determinar el número de recolecciones de elementos no utilizados que se produjeron en un intervalo de tiempo. Suponiendo el peor de los casos, la recolección de elementos no utilizados de la generación 0 décima finalizó al principio del segundo intervalo, y la recolección de elementos no utilizados de la generación 0 undécima finalizó al final del tercer intervalo. El tiempo entre el final de la décima y el final de la undécima recolección de basura es de aproximadamente 2 segundos, y el contador de rendimiento muestra 3%, por lo que la duración de la recolección de basura de generación 0 de la undécima vez fue de (2 segundos * 3% = 60 ms).

    En el ejemplo siguiente, hay cinco intervalos.

    Interval    Gen0    Gen1    Gen2     % Time in GC
            1       9       3       1                3
            2      10       3       1                1
            3      11       4       1                1
            4      11       4       1                1
            5      11       4       2               20
    

    La recolección de basura de la segunda generación comenzó durante el cuarto intervalo y finalizó en el quinto intervalo. Suponiendo que el peor de los casos, la última recolección de elementos no utilizados era para una recolección de generación 0 que finalizó al principio del tercer intervalo, y la recolección de elementos no utilizados de generación 2 finalizó al final del quinto intervalo. Por lo tanto, el tiempo entre el final de la recolección de elementos no utilizados de generación 0 y el final de la recolección de elementos no utilizados de generación 2 es de 4 segundos. Dado que el % Time in GC contador es 20%, la cantidad máxima de tiempo que podría haber tardado la recolección de elementos no utilizados de generación 2 es (4 segundos * 20% = 800 ms).

  • Como alternativa, puede determinar la duración de una recolección de basura mediante eventos ETW de recolección de basura y analizar esta información.

    Por ejemplo, los siguientes datos muestran una secuencia de eventos que ocurrió durante una recolección de basura no concurrente.

    Timestamp    Event name
    513052        GCSuspendEEBegin_V1
    513078        GCSuspendEEEnd
    513090        GCStart_V1
    517890        GCEnd_V1
    517894        GCHeapStats
    517897        GCRestartEEBegin
    517918        GCRestartEEEnd
    

    La suspensión del subproceso administrado tardó 26 us (GCSuspendEEEndGCSuspendEEBegin_V1).

    La recolección de basura real tomó 4,8 ms (GCEnd_V1GCStart_V1).

    La reanudación de los subprocesos administrados tardó 21 us (GCRestartEEEndGCRestartEEBegin).

    El resultado siguiente proporciona un ejemplo de recolección de elementos no utilizados en segundo plano, e incluye los campos de proceso, subproceso y evento. (No se muestran todos los datos).

    timestamp(us)    event name            process    thread    event field
    42504385        GCSuspendEEBegin_V1    Test.exe    4372             1
    42504648        GCSuspendEEEnd         Test.exe    4372
    42504816        GCStart_V1             Test.exe    4372        102019
    42504907        GCStart_V1             Test.exe    4372        102020
    42514170        GCEnd_V1               Test.exe    4372
    42514204        GCHeapStats            Test.exe    4372        102020
    42832052        GCRestartEEBegin       Test.exe    4372
    42832136        GCRestartEEEnd         Test.exe    4372
    63685394        GCSuspendEEBegin_V1    Test.exe    4744             6
    63686347        GCSuspendEEEnd         Test.exe    4744
    63784294        GCRestartEEBegin       Test.exe    4744
    63784407        GCRestartEEEnd         Test.exe    4744
    89931423        GCEnd_V1               Test.exe    4372        102019
    89931464        GCHeapStats            Test.exe    4372
    

    El GCStart_V1 evento en 42504816 indica que se trata de una recolección de basura en segundo plano, porque el último campo es 1. Esta se convierte en la recolección de elementos no utilizados número 102019.

    El evento GCStart se produce porque hay una necesidad de realizar una recolección de basura efímera antes de iniciar una recolección de basura en segundo plano. Esta se convierte en la recolección de elementos no utilizados n.º 102020.

    En 42514170, la recolección de elementos no utilizados número 102020 finaliza. Los subprocesos administrados se reinician en este momento. Esto se completa en el subproceso 4372, que desencadenó esta recolección de elementos no utilizados en segundo plano.

    En el subproceso 4744, se produce una suspensión. Esta es la última vez que la recolección de elementos no utilizados en segundo plano tiene que suspender subprocesos administrados. Esta duración es aproximadamente de 99 ms ((63784407-63685394)/1000).

    El evento GCEnd para la recolección de elementos no utilizados en segundo plano está en 89931423. Esto significa que la recolección de elementos no utilizados en segundo plano duró alrededor de 47 segundos ((89931423-42504816)/1000).

    Mientras los subprocesos administrados se están ejecutando, puede producirse cualquier número de recolecciones de elementos no utilizados efímeras.

Para determinar qué desencadenó una recolección de basura

  • En el depurador de WinDbg o Visual Studio con la extensión del depurador SOS cargada, escriba el siguiente comando para mostrar todos los subprocesos con sus pilas de llamadas:

    ~*Kb

    Este comando muestra una salida similar a la siguiente.

    0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect
    0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4
    0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48
    

    Si la recolección de elementos no utilizados se produjo por una notificación de memoria insuficiente del sistema operativo, la pila de llamadas es similar, salvo que el subproceso es el subproceso finalizador. El subproceso finalizador obtiene una notificación asincrónica de memoria insuficiente e induce la recolección de elementos no utilizados.

    Si la recolección de basura se debe a la asignación de memoria, la pila se muestra de la siguiente manera:

    0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration
    0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1
    0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18
    0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b
    0012f310 7a02ae4c mscorwks!Alloc+0x60
    0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd
    0012f424 300027f4 mscorwks!JIT_NewArr1+0x148
    000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c
    0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153
    

    Un asistente Just-In-Time (JIT_New*) llama finalmente a GCHeap::GarbageCollectGeneration. Si determina que las recolecciones de elementos no utilizados de generación 2 se deben a asignaciones, debe averiguar qué objetos recolecta una recolección de elementos no utilizados de generación 2 y cómo evitarlas. Es decir, quiere determinar la diferencia entre el inicio y el final de una recolección de basura de generación 2, y los objetos que la causaron.

    Por ejemplo, escriba el siguiente comando en el depurador para mostrar el principio de una colección de generación 2:

    !dumpheap –stat

    Salida de ejemplo (abreviada para mostrar los objetos que usan más espacio):

    79124228    31857      9862328 System.Object[]
    035f0384    25668     11601936 Toolkit.TlkPosition
    00155f80    21248     12256296      Free
    79103b6c   297003     13068132 System.Threading.ReaderWriterLock
    7a747ad4   708732     14174640 System.Collections.Specialized.HybridDictionary
    7a747c78   786498     15729960 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700298     19608344 System.Collections.Specialized.ListDictionary
    035f0ee4    89192     38887712 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    7912c444    91616     71887080 System.Double[]
    791242ec    32451     82462728 System.Collections.Hashtable+bucket[]
    790fa3e0  2459154    112128436 System.String
    Total 6471774 objects
    

    Repita el comando al final de la generación 2:

    !dumpheap –stat

    Salida de ejemplo (abreviada para mostrar los objetos que usan más espacio):

    79124228    26648      9314256 System.Object[]
    035f0384    25668     11601936 Toolkit.TlkPosition
    79103b6c   296770     13057880 System.Threading.ReaderWriterLock
    7a747ad4   708730     14174600 System.Collections.Specialized.HybridDictionary
    7a747c78   786497     15729940 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700298     19608344 System.Collections.Specialized.ListDictionary
    00155f80    13806     34007212      Free
    035f0ee4    89187     38885532 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    791242ec    32370     82359768 System.Collections.Hashtable+bucket[]
    790fa3e0  2440020    111341808 System.String
    Total 6417525 objects
    

    Los objetos double[] desaparecieron del final del resultado, lo que significa que se recopilaron. Estos objetos son aproximadamente de 70 MB. Los objetos restantes no cambiaron mucho. Por lo tanto, estos double[] objetos eran la razón por la cual ocurrió esta recolección de basura de generación 2. El siguiente paso es determinar por qué los double[] objetos están allí y por qué murieron. Puede preguntar al desarrollador de código de dónde proceden estos objetos o puede usar el gcroot comando .

Para determinar si el uso elevado de la CPU se debe a la recolección de elementos no utilizados

  • Correlacionar el valor del % Time in GC contador de rendimiento de memoria con el tiempo de proceso.

    Si el valor % Time in GC aumenta al mismo tiempo que el tiempo de proceso, la recolección de basura está causando un alto uso de CPU. De lo contrario, analice la aplicación para encontrar dónde se está produciendo el uso elevado.

Consulte también