¿Cuándo es aceptable llamar a GC.Collect?
El consejo general es que no debes llamar GC.Collect
desde tu código, pero ¿cuáles son las excepciones a esta regla?
Sólo se me ocurren unos pocos 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 una recopilación para evitar que el proceso que pronto quedará inactivo conserve más memoria de la necesaria.
¿Existen otros casos en los que sea aceptable llamar 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 de un pequeño impacto en el rendimiento. .
Un buen ejemplo de esto es si acaba de cerrar un formulario grande. Usted sabe que todos los controles de la interfaz de usuario ahora se pueden recolectar como basura, y una pausa muy breve cuando se cierra el formulario probablemente no será perceptible para el usuario.
ACTUALIZACIÓN 2.7.2018
A partir de .NET 4.5, existe GCLatencyMode.LowLatency
y GCLatencyMode.SustainedLowLatency
. Al entrar y salir de cualquiera de estos modos, se recomienda forzar una GC completa con GC.Collect(2, GCCollectionMode.Forced)
.
A partir de .NET 4.6, existe el GC.TryStartNoGCRegion
método (utilizado para establecer el valor de solo lectura GCLatencyMode.NoGCRegion
). Esto en sí mismo puede realizar una recolección de basura de bloqueo completo en un intento de liberar suficiente memoria, pero dado que no permitimos la GC por un período, yo diría que también es una buena idea realizar la GC completa antes y después.
Fuente: Ingeniero de Microsoft Ben Watson: Escritura de código .NET de alto rendimiento , 2.ª edición. 2018.
Ver:
- https://msdn.microsoft.com/en-us/library/system.runtime.gclatencymode(v=vs.110).aspx
- https://msdn.microsoft.com/en-us/library/dn906204(v=vs.110).aspx
Lo uso GC.Collect
sólo cuando escribo equipos de prueba de perfiladores/rendimiento crudos; es decir, tengo dos (o más) bloques de código para probar, algo 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);
...
De modo que TestA()
funcione TestB()
con el estado más similar posible, es decir, que TestB()
no se golpee solo porque TestA
lo dejó muy cerca del punto de inflexión.
Un ejemplo clásico sería un exe de consola simple (un Main
método lo suficientemente ordenado como para publicarlo aquí, por ejemplo), que muestra la diferencia entre la concatenación de cadenas en bucle y StringBuilder
.
Si necesito algo preciso, entonces serían dos pruebas completamente independientes, pero a menudo esto es suficiente si solo 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 la recolección de basura en la mayoría de los casos. (Todos los sistemas en los que he trabajado tenían recolecciones de basura forzadas, tenían problemas subrayados que, si se hubieran resuelto, habrían eliminado la necesidad de forzar la recolección de basura y aceleraron enormemente el sistema).
Hay algunos casos en los que usted sabe más sobre el uso de la memoria que el recolector de basura. 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 tipos de procesamiento por lotes, usted sabe más que el GC. Por ejemplo, considere una aplicación que.
- Se proporciona 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.
- Mientras se procesa el archivo, se crean una gran cantidad de objetos interconectados que no se pueden recopilar hasta que se haya completado el procesamiento del archivo (por ejemplo, un árbol de análisis).
- No mantiene el estado de coincidencia entre los archivos que ha procesado .
Es posible que pueda argumentar (después de realizar pruebas cuidadosas) que debe forzar una recolección de basura completa después de haber procesado 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 puede valer la pena forzar una recolección completa justo antes de irse a dormir.
El único momento en el que consideraría forzar una colección es cuando sé que se han creado 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 sugerencias sobre este tipo de cosas sin tener que forzar un GC por mi cuenta.
Véase también " Cositas de actuación de Rico Mariani "
Hoy en día considero que en los casos anteriores sería mejor utilizar un proceso de trabajo de corta duración para realizar cada lote de trabajo y dejar que el sistema operativo se encargue de la recuperación de recursos.
Un caso es cuando intenta realizar una prueba unitaria de código que utiliza WeakReference .
En sistemas grandes que funcionan 24 horas al día, 7 días a la semana o 24 horas al día, 6 días a la semana (sistemas que reaccionan a mensajes, solicitudes RPC o que sondean una base de datos o proceso continuamente) es útil tener una forma de identificar pérdidas de memoria. Para esto, tiendo a agregar un mecanismo a la aplicación para suspender temporalmente cualquier procesamiento y luego realizar una recolección de basura completa. Esto pone al sistema en un estado inactivo donde la memoria restante es legítimamente de larga duración (cachés, configuración, etc.) o se "filtra" (objetos que no se espera o no se desea que sean rooteados, pero que en realidad lo son).
Tener este mecanismo hace que sea mucho más fácil perfilar el uso de la memoria, ya que los informes no se verán empañados por el ruido del procesamiento activo.
Para asegurarse de obtener toda la basura, debe realizar dos recolecciones:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Como la primera colección, se finalizarán todos los objetos con finalizadores (pero no se recolectará la basura de estos objetos). El segundo GC recolectará basura de estos objetos finalizados.