¿Cuándo es aceptable llamar a GC.Collect?

El consejo general es que no debe llamar a GC.Collect desde su código, pero ¿cuáles son las excepciones a esta regla?

Solo puedo pensar en algunos casos muy específicos en los que puede tener sentido forzar una recolección de basura.

Un ejemplo que me viene a la mente es un servicio que se despierta a intervalos, realiza alguna tarea y luego duerme durante un largo tiempo. En este caso, puede ser una buena idea forzar un recostacktorio para evitar que el proceso, que pronto estará inactivo, conserve más memoria de la necesaria.

¿Hay algún otro caso en que sea aceptable llamar a GC.Collect ?

Si tiene buenas razones para creer que un conjunto significativo de objetos -particularmente aquellos que sospecha que están en las generaciones 1 y 2- ahora son elegibles para la recolección de basura, y que ahora sería un momento apropiado para recolectar en términos del pequeño golpe de rendimiento .

Un buen ejemplo de esto es si acabas de cerrar una forma grande. Usted sabe que todos los controles de UI ahora pueden ser basura, y una pausa muy corta ya que el formulario está cerrado probablemente no será notorio para el usuario.

ACTUALIZACIÓN 2.7.2018

A partir de .NET 4.5, hay GCLatencyMode.LowLatency y GCLatencyMode.SustainedLowLatency . Al ingresar y abandonar cualquiera de estos modos, se recomienda forzar un GC completo con GC. GC.Collect(2, GCCollectionMode.Forced) .

A partir de .NET 4.6, existe el método GC.TryStartNoGCRegion (utilizado para establecer el valor de solo lectura GCLatencyMode.NoGCRegion ). Esto puede, por sí solo, realizar una recolección completa de basura bloqueada en un bash de liberar suficiente memoria, pero dado que estamos rechazando GC por un período, yo diría que también es una buena idea realizar un GC completo antes y después.

Fuente: Ingeniero de Microsoft, Ben Watson: escritura de código .NET de alto rendimiento , 2da edición. 2018.

Ver:

Yo uso GC.Collect solo cuando escribo equipos de pruebas de desempeño crudo / perfilador; es decir, tengo dos (o más) bloques de código para probar, algo así como:

 GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); TestA(); // may allocate lots of transient objects GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); TestB(); // may allocate lots of transient objects GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); ... 

Para que TestA() y TestB() ejecuten con el estado más similar posible, es decir, TestB() no se TestA solo porque TestA dejó muy cerca del punto de inflexión.

Un ejemplo clásico sería un simple console exe (una clasificación de método Main , suficiente para publicar aquí, por ejemplo), que muestra la diferencia entre la concatenación de cadenas en bucle y StringBuilder .

Si necesito algo preciso, esta sería dos pruebas completamente independientes, pero a menudo esto es suficiente si queremos minimizar (o normalizar) el GC durante las pruebas para tener una idea aproximada del comportamiento.

Durante el código de producción? Todavía tengo que usarlo ;-p

La mejor práctica es no forzar una recolección de basura en la mayoría de los casos. (Todos los sistemas en los que he trabajado que forzaron recolecciones de basura tenían problemas subyacentes que, de resolverse, habrían eliminado la necesidad de forzar la recolección de basura y acelerarían mucho el sistema).

Hay algunos casos en los que sabe más sobre el uso de memoria que el recolector de elementos no utilizados. Es poco probable que esto sea cierto en una aplicación multiusuario o en un servicio que responde a más de una solicitud a la vez.

Sin embargo, en algunos procesos de tipo por lotes, usted sabe más que el GC. Por ejemplo, considere una aplicación que.

  • Se le da una lista de nombres de archivos en la línea de comando
  • Procesa un solo archivo y luego escribe el resultado en un archivo de resultados.
  • Al procesar el archivo, crea una gran cantidad de objetos interconectados que no se pueden recostackr hasta que el procesamiento del archivo se haya completado (por ejemplo, un árbol de análisis sintáctico)
  • No mantiene el estado de coincidencia entre los archivos que ha procesado .

Es posible que pueda realizar una prueba de caso (después de la cuidadosa) de que debe forzar una recolección completa de basura después de procesar cada archivo.

Otro caso es un servicio que se activa cada pocos minutos para procesar algunos elementos y no mantiene ningún estado mientras está dormido . Entonces, forzar una colección completa justo antes de ir a dormir puede valer la pena.

La única vez que consideraría forzar una colección es cuando sé que se crearon muchos objetos recientemente y que actualmente se hace referencia a muy pocos objetos.

Preferiría tener una API de recolección de basura cuando pudiera darle pistas sobre este tipo de cosas sin tener que forzar una GC.

Ver también ” Tidbits de rendimiento de Rico Mariani ”

Un caso es cuando intentas un código de prueba unitaria que usa WeakReference .

En sistemas grandes 24/7 o 24/6 – sistemas que reactjsn a mensajes, solicitudes RPC o que sondean una base de datos o procesan continuamente – es útil tener una forma de identificar memory leaks. Para esto, tiendo a agregar un mecanismo a la aplicación para suspender temporalmente cualquier procesamiento y luego realizar una recolección completa de basura. Esto pone el sistema en un estado inactivo donde la memoria restante es legítima memoria de larga duración (cachés, configuración, & c.) O bien se ‘filtró’ (objetos que no se esperaban o que deseaban rootear pero que en realidad son).

Tener este mecanismo hace que sea mucho más fácil hacer un perfil de uso de memoria ya que los informes no se nublarán con el ruido del procesamiento activo.

Para asegurarse de obtener toda la basura, necesita realizar dos colecciones:

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

Como la primera colección causará que los objetos con finalizadores estén finalizados (pero no basura en realidad recostackn estos objetos). El segundo GC recogerá estos objetos finalizados.

Eche un vistazo a este artículo de Rico Mariani. Él da dos reglas cuando llamar a GC.Collect (la regla 1 es: “Do not”):

Cuándo llamar a GC.Collect ()

Puede llamar a GC.Collect () cuando sepa algo sobre la naturaleza de la aplicación que el recolector de basura no. Es tentador pensar que, como autor, esto es muy probable. Sin embargo, la verdad es que el GC equivale a un sistema experto bastante bien escrito y probado, y es raro que usted sepa algo acerca de las rutas de código de bajo nivel que no lo hace.

El mejor ejemplo en el que puedo pensar donde podría tener algo de información extra es una aplicación que hace ciclos entre períodos inactivos y períodos muy ocupados. Desea el mejor rendimiento posible para los períodos muy ocupados y, por lo tanto, desea utilizar el tiempo de inactividad para realizar una limpieza.

Sin embargo, la mayoría de las veces el GC es lo suficientemente inteligente como para hacer esto de todos modos.

Como una solución de fragmentación de memoria. Estaba agotando las excepciones de memoria mientras escribía muchos datos en una secuencia de memoria (leyendo de un flujo de red). Los datos fueron escritos en fragmentos de 8K. Después de llegar a 128M hubo una excepción a pesar de que había mucha memoria disponible (pero estaba fragmentada). Llamar a GC.Collect () resolvió el problema. Pude manejar más de 1G después de la corrección.

Estaba haciendo algunas pruebas de rendimiento en array y list:

 private static int count = 100000000; private static List GetSomeNumbers_List_int() { var lstNumbers = new List(); for(var i = 1; i <= count; i++) { lstNumbers.Add(i); } return lstNumbers; } private static int[] GetSomeNumbers_Array() { var lstNumbers = new int[count]; for (var i = 1; i <= count; i++) { lstNumbers[i-1] = i + 1; } return lstNumbers; } private static int[] GetSomeNumbers_Enumerable_Range() { return Enumerable.Range(1, count).ToArray(); } static void performance_100_Million() { var sw = new Stopwatch(); sw.Start(); var numbers1 = GetSomeNumbers_List_int(); sw.Stop(); //numbers1 = null; //GC.Collect(); Console.WriteLine(String.Format("\"List\" took {0} milliseconds", sw.ElapsedMilliseconds)); sw.Reset(); sw.Start(); var numbers2 = GetSomeNumbers_Array(); sw.Stop(); //numbers2 = null; //GC.Collect(); Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds)); sw.Reset(); sw.Start(); //getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method var numbers3 = GetSomeNumbers_Enumerable_Range(); sw.Stop(); //numbers3 = null; //GC.Collect(); Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds)); } 

y obtuve OutOfMemoryException en el método GetSomeNumbers_Enumerable_Range, la única solución es desasignar la memoria de la siguiente manera:

 numbers = null; GC.Collect(); 

En su ejemplo, creo que llamar GC.Collect no es el problema, sino que hay un problema de diseño.

Si va a despertarse a intervalos, (establecer tiempos), entonces su progtwig debe diseñarse para una sola ejecución (realizar la tarea una vez) y luego terminar. Luego, establece el progtwig como una tarea progtwigda para ejecutar en los intervalos progtwigdos.

De esta manera, no tiene que preocuparse por llamar a GC.Collect, (que rara vez, si alguna vez, tiene que hacer).

Dicho esto, Rico Mariani tiene una excelente publicación de blog sobre este tema, que se puede encontrar aquí:

http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx

Un lugar útil para llamar a GC.Collect () está en una prueba unitaria cuando desea verificar que no está creando una pérdida de memoria (por ejemplo, si está haciendo algo con WeakReferences o ConditionalWeakTable, código generado dinámicamente, etc.).

Por ejemplo, tengo algunas pruebas como:

 WeakReference w = CodeThatShouldNotMemoryLeak(); Assert.IsTrue(w.IsAlive); GC.Collect(); GC.WaitForPendingFinalizers(); Assert.IsFalse(w.IsAlive); 

Podría argumentarse que el uso de WeakReferences es un problema en sí mismo, pero parece que si está creando un sistema que se basa en dicho comportamiento, entonces llamar a GC.Collect () es una buena manera de verificar dicho código.

La respuesta corta es: ¡nunca!

 using(var stream = new MemoryStream()) { bitmap.Save(stream, ImageFormat.Png); techObject.Last().Image = Image.FromStream(stream); bitmap.Dispose(); // Without this code, I had an OutOfMemory exception. GC.Collect(); GC.WaitForPendingFinalizers(); // } 

Hay algunas situaciones en las que es mejor prevenir que lamentar.

Aquí hay una situación.

Es posible crear una DLL no administrada en C # utilizando IL reescrituras (porque hay situaciones en las que esto es necesario).

Ahora supongamos, por ejemplo, que el DLL crea una matriz de bytes en el nivel de clase, porque muchas de las funciones exportadas necesitan acceso a tal. ¿Qué sucede cuando la DLL está descargada? ¿Se llama automáticamente al recolector de basura en ese punto? No sé, pero al ser una DLL no administrada , es completamente posible que no se llame al GC. Y sería un gran problema si no se llama. Cuando se descarga el archivo DLL, también lo sería el recolector de basura. Entonces, ¿quién se encargará de recoger cualquier posible basura y cómo lo harán? Es mejor emplear el recolector de basura de C #. Tener una función de limpieza (disponible para el cliente DLL) donde las variables de nivel de clase se establecen como nulas y se llama al recolector de elementos no utilizados.

Más vale prevenir que curar.

La entrada del blog de Scott Holden sobre cuándo (y cuándo no) llamar a GC.Collect es específico de .NET Compact Framework , pero las reglas generalmente se aplican a todo el desarrollo administrado.

Todavía estoy bastante inseguro sobre esto. Estoy trabajando desde hace 7 años en un servidor de aplicaciones. Nuestras instalaciones más grandes utilizan 24 GB Ram. Su gran multiproceso, y TODAS las llamadas a GC.Collect () se encontraron con problemas de rendimiento realmente terribles.

Muchos componentes de terceros utilizaron GC.Collect () cuando pensaron que era inteligente hacerlo en este momento. Entonces, un simple grupo de Excel-Reports bloqueó el Servidor de aplicaciones para todos los hilos varias veces por minuto.

Tuvimos que refactorizar todos los componentes de terceros para eliminar las llamadas GC.Collect (), y todo funcionó bien después de hacer esto.

Pero también estoy ejecutando servidores en Win32, y aquí comencé a utilizar GC.Collect () después de obtener una OutOfMemoryException.

Pero también estoy bastante inseguro sobre esto, porque a menudo me di cuenta, cuando obtengo un OOM en 32 Bit, y vuelvo a intentar ejecutar la misma Operación nuevamente, sin llamar a GC.Collect (), simplemente funcionó bien.

Una cosa que me pregunto es la Excepción OOM en sí … Si hubiera escrito .Net Framework, y no puedo asignar un bloque de memoria, usaría GC.Collect (), defrag memory (??), intentaría de nuevo , y si aún no puedo encontrar un bloque de memoria libre, lanzaría la OOM-Exception.

O al menos haga que este comportamiento sea una opción configurable, debido a los inconvenientes del problema de rendimiento con GC.Collect.

Ahora tengo muchos códigos como este en mi aplicación para “resolver” el problema:

 public static TResult ExecuteOOMAware(Func func, T1 a1, T2 a2) { int oomCounter = 0; int maxOOMRetries = 10; do { try { return func(a1, a2); } catch (OutOfMemoryException) { oomCounter++; if (maxOOMRetries > 10) { throw; } else { Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString()); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10)); GC.Collect(); } } } while (oomCounter < maxOOMRetries); // never gets hitted. return default(TResult); } 

(Tenga en cuenta que el comportamiento Thread.Sleep () es realmente un comportamiento Apecific de la aplicación, porque estamos ejecutando un servicio de almacenamiento en caché ORM, y el servicio tarda algo de tiempo en liberar todos los objetos en caché, si la RAM excede algunos valores predefinidos. unos segundos la primera vez, y ha aumentado el tiempo de espera cada vez que aparece OOM).

Debería evitar usar GC.Collect () ya que es muy caro. Aquí hay un ejemplo:

  public void ClearFrame(ulong timeStamp) { if (RecordSet.Count <= 0) return; if (Limit == false) { var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000; if (seconds <= _preFramesTime) return; Limit = true; do { RecordSet.Remove(RecordSet[0]); } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime); } else { RecordSet.Remove(RecordSet[0]); } GC.Collect(); // AVOID } 

RESULTADO DE LA PRUEBA: UTILIZACIÓN DE LA CPU 12%

Cuando cambias a esto:

  public void ClearFrame(ulong timeStamp) { if (RecordSet.Count <= 0) return; if (Limit == false) { var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000; if (seconds <= _preFramesTime) return; Limit = true; do { RecordSet[0].Dispose(); // Bitmap destroyed! RecordSet.Remove(RecordSet[0]); } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime); } else { RecordSet[0].Dispose(); // Bitmap destroyed! RecordSet.Remove(RecordSet[0]); } //GC.Collect(); } 

RESULTADO DE LA PRUEBA: USO DE LA CPU 2-3%

Una instancia en la que es casi necesario llamar a GC.Collect () es cuando se automatiza Microsoft Office a través de Interop. A los objetos COM para Office no les gusta que se liberen automáticamente y pueden dar lugar a que las instancias del producto de Office ocupen grandes cantidades de memoria. No estoy seguro de si esto es un problema o por diseño. Hay muchas publicaciones sobre este tema en Internet, así que no entraré en demasiados detalles.

Cuando se progtwig con Interop, cada objeto COM se debe liberar manualmente, generalmente mediante el uso de Marshal.ReleseComObject (). Además, llamar a Garbage Collection manualmente puede ayudar a “limpiar” un poco. Llamar al siguiente código cuando haya terminado con los objetos Interop parece ayudar bastante:

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

En mi experiencia personal, usar una combinación de ReleaseComObject y llamar manualmente a la recolección de basura reduce en gran medida el uso de memoria de los productos de Office, específicamente Excel.

Esto no es relevante para la pregunta, pero para las transformaciones XSLT en .NET (XSLCompiledTranform), es posible que no tenga otra opción. Otro candidato es el control MSHTML.

Si está utilizando una versión de .net menor que 4.5, la recostackción manual puede ser inevitable (especialmente si está tratando con muchos ‘objetos grandes’).

este enlace describe por qué:

https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/

Una buena razón para llamar a GC es en pequeñas computadoras ARM con poca memoria, como el Raspberry PI (que funciona con mono). Si los fragmentos de memoria no asignados utilizan demasiada memoria RAM del sistema, entonces el sistema operativo Linux puede volverse inestable. Tengo una aplicación donde tengo que llamar a GC cada segundo (!) Para deshacerme de los problemas de desbordamiento de memoria.

Otra buena solución es deshacerse de los objetos cuando ya no los necesiten. Desafortunadamente esto no es tan fácil en muchos casos.

Dado que hay Small Heap Heap (SOH) y Large Object Heap (LOH)

Podemos llamar a GC.Collect () para borrar el objeto de desreferencia en SOP y mover el objeto vivido a la siguiente generación.

En .net4.5, también podemos compactar LOH utilizando largeobjectheapcompactionmode