Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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.
- Se produce una excepción de memoria insuficiente
- El proceso usa demasiada memoria.
- El recolector de basura no recupera objetos lo suficientemente rápido
- El montón administrado está demasiado fragmentado
- Las pausas de recolección de basura son demasiado largas
- La generación 0 es demasiado grande
- El uso de CPU durante una recolección de elementos no utilizados es demasiado alto
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.
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.
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:
- Determine si el problema se debe a la recolección de basura.
- 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.
- 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.
- Determine si se ha ejecutado un finalizador.
- Determine si hay objetos a la espera de finalizar.
- Determine la cantidad de espacio libre en el montón administrado.
- Determine el número de objetos anclados.
- Determine la duración de una recolección de elementos no utilizados.
- Determine lo que desencadenó una recolección de basura.
- Determine si el uso elevado de la CPU se debe a la recolección de elementos no utilizados.
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
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):
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
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
Inicie el Administrador de tareas de Windows.
En la pestaña
Performance
, observe el valor confirmado. (En Windows 7, mira elCommit (KB)
en elSystem group
.)Si el
Total
está cerca delLimit
, 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 quegcroot
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
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.
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
, yGen2
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 (
GCSuspendEEEnd
–GCSuspendEEBegin_V1
).La recolección de basura real tomó 4,8 ms (
GCEnd_V1
–GCStart_V1
).La reanudación de los subprocesos administrados tardó 21 us (
GCRestartEEEnd
–GCRestartEEBegin
).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 es1
. 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 aGCHeap::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, estosdouble[]
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é losdouble[]
objetos están allí y por qué murieron. Puede preguntar al desarrollador de código de dónde proceden estos objetos o puede usar elgcroot
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.