returns remarks method documentacion description comentarios c# .net garbage-collection .net-4.5

method - remarks c#



¿La colección gen2 no siempre recoge objetos muertos? (6)

Al monitorear los CLR #Bytes in all Heaps contadores de rendimiento de una nueva aplicación de servidor .NET 4.5 en los últimos días, puedo notar un patrón que me hace pensar que la colección Gen2 no siempre está recolectando objetos muertos, pero tengo problemas entendiendo que es exactamente lo que esta pasando

La aplicación de servidor se está ejecutando en .NET Framework 4.5.1 utilizando Server GC / Background.

Esta es una aplicación de consola alojada como un servicio de Windows (con la ayuda del marco Topshelf)

La aplicación del servidor está procesando mensajes, y el rendimiento es bastante constante por ahora.

Lo que puedo ver al ver el gráfico de CLR #Bytes in all Heaps es que la memoria comenzó alrededor de 18 MB y luego creció hasta 35 MB en aproximadamente 20-24 horas (con entre 20-30 colecciones Gen2 durante ese período de tiempo), y luego todo de una caída repentina a un valor nominal de 18 MB, luego volverá a crecer hasta ~ 35 MB en 20-24 horas y volverá a caer en 18 MB, y así sucesivamente (puedo ver el patrón repetido en los últimos 6 días en que la aplicación se está ejecutando) ... El crecimiento de la memoria no es lineal, toma aproximadamente 5 horas crecer 10 MB y luego 15-17 horas para los 10 MB restantes.

La cosa es que puedo ver al mirar los contadores de perfmon para las #Gen0/#Gen1/#Gen2 collections que un grupo de colecciones Gen2 están funcionando durante el período de 20 a 24 horas (quizás alrededor de 30) y ninguna de ellas hace que la memoria se caiga de vuelta a nominal 18MB. Sin embargo, lo que es extraño es usar una herramienta externa para forzar un GC (Perfview en mi caso), luego puedo ver #Induced GC subiendo en 1 (se llamó GC.Collect, así que esto es normal) e inmediatamente la memoria va de vuelta a nominal 18MB.

Lo que me lleva a pensar que el contador de perfmon para las colecciones # Gen2 no es correcto y solo una colección Gen2 ocurre después de 20-22 horas aproximadamente (meeehhh, realmente no lo creo) o que la colección Gen2 no siempre cobra muertos objetos (parece más plausible) ... pero en ese caso, ¿por qué forzar un GC a través de GC.Collect hará el truco, cuál sería la diferencia entre llamar explícitamente a GC.Collect, frente a las colecciones activadas automáticamente durante la vida útil de la aplicación?

Estoy seguro de que hay una muy buena explicación, pero de la diferente fuente de documentación que he encontrado sobre GC-muy pocas: (- en cualquier caso, una colección Gen2 recopila objetos muertos. Por lo tanto, es posible que los documentos no estén actualizados o que haya leído mal. ... Cualquier explicación es bienvenida. Gracias!

EDITAR: Por favor, vea esta captura de pantalla de los #Bytes in all heaps gráfico de #Bytes in all heaps más de 4 días

(Haga clic para ampliar)

esto es más fácil que tratar de graficar cosas en tu cabeza. Lo que puede ver en el gráfico es lo que dije anteriormente ... la memoria aumenta de 20 a 24 horas (y de 20 a 30 colecciones de Gen2 durante ese período de tiempo) hasta que alcanza ~ 35 MB y luego cae repentinamente. Notará que al final de la gráfica, el GC I inducido se disparó a través de una herramienta externa, y de este modo la memoria se redujo inmediatamente a la nominal.

EDIT # 2: Realicé muchas limpiezas en el código, principalmente con respecto a los finalizadores. Tenía muchas clases que contenían referencias a tipos desechables, así que tuve que implementar IDisposable en estos tipos. Sin embargo, fui engañado por algunos artículos para implementar el patrón Diposable con un Finalizer en cualquier caso. Después de leer algunos documentos de MSDN comprendí que solo se necesitaba un finalizador cuando el tipo contuviera los recursos nativos (y, en ese caso, esto podría evitarse con SafeHandle). Así que eliminé todos los finalizadores de todos estos tipos. Hubo algunas otras modificaciones en el código, pero principalmente en la lógica de negocios, nada relacionado con ".NET framework". Ahora el gráfico es muy diferente, esta es una línea plana de 20 MB por días ahora ... ¡exactamente lo que esperaba ver! Así que el problema ya está solucionado, sin embargo, todavía no tengo idea de cuál fue el problema debido a ... Parece que podría haber estado relacionado con los finalizadores, pero aún no explica lo que estaba notando, incluso si no estuviéramos llamando a Desechar (verdadero) -suppressing finalizer-, ¡el hilo del finalizador se supone que se activa entre la colección y no cada 20-24 horas? Teniendo en cuenta que ahora nos hemos alejado del problema, llevará tiempo volver a la versión "con errores" y volver a reproducirla. Aunque podría intentar hacerlo un poco de tiempo e ir al fondo.

EDITAR: Se agregó el gráfico de la colección Gen2 (haga clic para ampliar)



Desde

http://msdn.microsoft.com/en-us/library/ee787088%28v=VS.110%29.aspx#workstation_and_server_garbage_collection

Condiciones para una recogida de basura.

La recolección de basura se produce cuando se cumple una de las siguientes condiciones:

  • El sistema tiene poca memoria física.

  • La memoria que utilizan los objetos asignados en el montón administrado supera un umbral aceptable. Este umbral se ajusta continuamente a medida que se ejecuta el proceso.

  • Se llama al método GC.Collect. En casi todos los casos, no tiene que llamar a este método, porque el recolector de basura se ejecuta continuamente. Este método se utiliza principalmente para situaciones y pruebas únicas.

Parece que estás golpeando el segundo y 35 es el umbral. Debería poder configurar el umbral a otra cosa si 35 es demasiado grande.

No hay nada especial acerca de las colecciones gen2 que causen que se desvíen de estas reglas. (cf https://.com/a/8582251/215752 )


Esto podría explicarse fácilmente si gcTrimCommitOnLowMemory está habilitado. Normalmente, el GC mantiene alguna memoria extra asignada al proceso. Sin embargo, cuando la memoria alcanza un cierto umbral, el GC "recortará" la memoria adicional.

De la documentación:

Cuando se habilita la configuración gcTrimCommitOnLowMemory, el recolector de basura evalúa la carga de la memoria del sistema y entra en un modo de recorte cuando la carga alcanza el 90%. Mantiene el modo de recorte hasta que la carga cae por debajo del 85%.

Esto podría explicar fácilmente su situación: las reservas de memoria se mantienen (y se usan) hasta que su aplicación llega a un punto determinado, que parece ser una vez cada 20-24 horas, momento en el que se detecta la carga del 90% y la memoria es recortado a sus requisitos mínimos (los 18mb).


Leyendo tu primera versión diría que es un comportamiento normal.

... pero en ese caso, ¿por qué obligar a un GC a través de GC.Collect a hacer el truco, cuál sería la diferencia entre llamar explícitamente a GC.Collect, frente a las colecciones activadas automáticamente durante la vida útil de la aplicación?

Hay dos tipos de colecciones, una colección completa y una colección parcial. Lo que hace el disparo automático es una colección parcial, pero al llamar a GC.Collect hará una colección completa.

Mientras tanto, podría tener la razón de ello ahora que nos dijo que estaba usando el finalizador en todos sus objetos. Si, por alguna razón, uno de esos objetos se promoviera a # 2 Gen, el finalizador solo se ejecutaría al realizar una colección # 2 Gen.

El siguiente ejemplo demostrará lo que acabo de decir.

public class ClassWithFinalizer { ~ClassWithFinalizer() { Console.WriteLine("hello from finalizer"); //do nothing } } static void Main(string[] args) { ClassWithFinalizer a = new ClassWithFinalizer(); Console.WriteLine("Class a is on #{0} generation", GC.GetGeneration(a)); GC.Collect(); Console.WriteLine("Class a is on #{0} generation", GC.GetGeneration(a)); GC.Collect(); Console.WriteLine("Class a is on #{0} generation", GC.GetGeneration(a)); a = null; Console.WriteLine("Collecting 0 Gen"); GC.Collect(0); GC.WaitForPendingFinalizers(); Console.WriteLine("Collecting 0 and 1 Gen"); GC.Collect(1); GC.WaitForPendingFinalizers(); Console.WriteLine("Collecting 0, 1 and 2 Gen"); GC.Collect(2); GC.WaitForPendingFinalizers(); Console.Read(); }

La salida será:

Class a is on #0 generation Class a is on #1 generation Class a is on #2 generation Collecting 0 Gen Collecting 0 and 1 Gen Collecting 0, 1 and 2 Gen hello from finalizer

Como puede ver, solo cuando se realiza una recopilación en la generación donde está el objeto, se recuperará la memoria de los objetos con finalizador.


Solo imagino que voy a tirar mis 2 centavos aquí. No soy un experto en esto, pero tal vez esto pueda ayudar a su investigación.

Si está utilizando una plataforma de 64 bits, intente agregar esto a su archivo .config. Leí que eso puede ser un problema.

<configuration> <runtime> <gcAllowVeryLargeObjects enabled="true" /> </runtime> </configuration>

Lo único que quisiera señalar es que usted podría probar su hipótesis resolviendo problemas desde adentro si tiene el control del código fuente.

Llamar a algo en línea con la clase que consume memoria principal de tu aplicación y configurarlo para que se ejecute en intervalos de tiempo, podría arrojar algo de luz sobre lo que realmente está sucediendo.

private void LogGCState() { int gen = GC.GetGeneration(this); //------------------------------------------ // Comment out the GC.GetTotalMemory(true) line to see what''s happening // without any interference //------------------------------------------ StringBuilder sb = new StringBuilder(); sb.Append(DateTime.Now.ToString("G")).Append(''/t''); sb.Append("MaxGens: ").Append(GC.MaxGeneration).Append(''/t''); sb.Append("CurGen: ").Append(gen).Append(''/t''); sb.Append("CurGenCount: ").Append(GC.CollectionCount(gen)).Append(''/t''); sb.Append("TotalMemory: ").Append(GC.GetTotalMemory(false)).Append(''/t''); sb.Append("AfterCollect: ").Append(GC.GetTotalMemory(true)).Append("/r/n"); File.AppendAllText(@"C:/GCLog.txt", sb.ToString()); }

También hay un artículo bastante bueno here sobre el uso del método GC.RegisterForFullGCNotification . Obviamente, esto le permitiría incluir también el período de tiempo de una colección completa para que pueda ajustar el rendimiento y la frecuencia de la colección a sus necesidades específicas. Este método también le permite especificar un umbral de almacenamiento dinámico para activar notificaciones (¿o colecciones?).

También es probable que haya una forma de configurarlo en el archivo .config de aplicaciones, pero no lo he buscado. En su mayor parte, 35 MB es una huella bastante pequeña para una aplicación de servidor en estos días. Heck, mi navegador web alcanza 300-400MB a veces :) Por lo tanto, el Framework podría ver 35MB como un buen punto predeterminado para liberar memoria.

De todos modos, puedo decir por la consideración de tu pregunta que probablemente solo estoy señalando lo obvio. Pero, vale la pena mencionarlo. ¡Te deseo suerte!.

En una nota divertida

En la parte superior de esta publicación originalmente escribí "si (estás usando una plataforma de 64 bits)". Eso me hizo reír. ¡Cuídate!


Tengo exactamente la misma situación en mi aplicación WPF. No hay finalizadores en mi código por cierto. Sin embargo, parece que el GC en curso realmente recopila objetos Gen 2. Puedo ver que los resultados de GC.GetTotalMemory () se reducen hasta 150 mb después de que se desencadena la recopilación de Gen2.

Así que tengo la impresión de que el tamaño del montón de Gen2 no muestra la cantidad de bytes que utilizan los objetos vivos. Es más bien un tamaño de pila o cantidad de bytes que se asigna para fines de Gen2. Puede tener un montón de memoria libre allí.

Bajo algunas condiciones (no en cada colección gen 2) este tamaño de pila se recorta. Y en este momento en particular, mi aplicación tiene un gran impacto en el rendimiento: puede colgar hasta segundos segundos. Preguntándome por qué...