Realmente no puede hacer declaraciones generales sobre la forma adecuada de utilizar todas las implementaciones de GC. Varían enormemente. Así que hablaré con el .NET al que te referiste originalmente.
Debe conocer el comportamiento del GC bastante íntimamente para hacerlo con lógica o razón.
El único consejo sobre la colección que puedo dar es: nunca lo hagas.
Si realmente conoce los intrincados detalles del GC, no necesitará mi consejo, por lo que no importará. Si no lo sabe ya con 100% de confianza que le ayudará, y tienen que buscar en línea y encontrar una respuesta como esta: Usted no debe estar llamando GC.Collect , o alternativamente: Usted debe ir a conocer los detalles de cómo funciona el GC por dentro y por fuera, y solo entonces sabrás la respuesta .
Hay un lugar seguro donde tiene sentido usar GC .
GC.Collect es una API disponible que puede usar para perfilar los tiempos de las cosas. Puede perfilar un algoritmo, recopilar y perfilar otro algoritmo inmediatamente después sabiendo que GC del primer algo no estaba ocurriendo durante el segundo sesgando los resultados.
Este tipo de perfil es la única vez que sugeriría recopilar manualmente a cualquiera.
Ejemplo contribuido de todos modos
Un posible caso de uso es que si carga cosas realmente grandes, terminarán en el Montón de objetos grandes que irá directamente a Gen 2, aunque nuevamente Gen 2 es para objetos de larga vida porque se acumula con menos frecuencia. Si sabe que está cargando objetos de corta duración en Gen 2 por cualquier motivo, puede borrarlos más rápidamente para mantener su Gen 2 más pequeño y sus colecciones más rápido.
Este es el mejor ejemplo que se me ocurre, y no es bueno: la presión de LOH que está generando aquí provocaría colecciones más frecuentes, y las colecciones son tan frecuentes como es posible, es probable que elimine el LOH de la misma manera que tan rápido como lo soplabas con objetos temporales. Simplemente no confío en mí mismo para presumir una mejor frecuencia de recolección que el GC en sí, sintonizado por personas mucho más inteligentes que yo.
Así que hablemos sobre algunas de las semánticas y mecanismos en .NET GC ... o ...
Todo lo que creo saber sobre el GC .NET
Por favor, cualquiera que encuentre errores aquí, corrígeme. Se sabe que gran parte de la GC es magia negra y, aunque traté de omitir detalles de los que no estaba seguro, probablemente todavía me equivoqué.
A continuación, faltan intencionalmente numerosos detalles de los que no estoy seguro, así como una gran cantidad de información que simplemente desconozco. Utilice esta información bajo su propio riesgo.
Conceptos de GC
El .NET GC ocurre en momentos inconsistentes, por eso se llama "no determinista", esto significa que no puede confiar en que ocurra en momentos específicos. También es un recolector de basura generacional, lo que significa que divide sus objetos en la cantidad de GC que han pasado.
Los objetos en el montón Gen 0 han vivido a través de 0 colecciones, estas se han hecho recientemente, por lo que no se ha producido ninguna colección desde su creación. Los objetos en su montón Gen 1 han vivido a través de un pase de colección, y del mismo modo los objetos en su montón Gen 2 han vivido a través de 2 pases de colección.
Ahora vale la pena señalar la razón por la que califica estas generaciones y particiones específicas en consecuencia. .NET GC solo reconoce estas tres generaciones, porque los pases de recolección que pasan por estos tres montones son todos ligeramente diferentes. Algunos objetos pueden sobrevivir a la recolección de miles de veces. El GC simplemente los deja al otro lado de la partición de almacenamiento dinámico Gen 2, no tiene sentido dividirlos en otro lugar porque en realidad son Gen 44; el pase de colección sobre ellos es el mismo que todo en el montón de Gen 2.
Hay propósitos semánticos para estas generaciones específicas, así como mecanismos implementados que los honran, y los abordaré en un momento.
¿Qué hay en una colección?
El concepto básico de un pase de colección GC es que verifica cada objeto en un espacio de almacenamiento dinámico para ver si todavía hay referencias vivas (raíces GC) a estos objetos. Si se encuentra una raíz GC para un objeto, significa que el código que se está ejecutando actualmente puede alcanzar y usar ese objeto, por lo que no se puede eliminar. Sin embargo, si no se encuentra una raíz GC para un objeto, significa que el proceso en ejecución ya no necesita el objeto, por lo que puede eliminarlo para liberar memoria para nuevos objetos.
Ahora, una vez que haya terminado de limpiar un montón de objetos y dejar algunos en paz, habrá un efecto secundario desafortunado: espacios libres entre los objetos vivos donde se eliminaron los muertos. Esta fragmentación de la memoria, si se deja sola, simplemente desperdiciaría la memoria, por lo que las colecciones normalmente harán lo que se llama "compactación", donde toman todos los objetos vivos que quedan y los aprietan en el montón para que la memoria libre sea contigua en un lado del montón para Gen 0.
Ahora, dada la idea de 3 montones de memoria, todos particionados por el número de pases de colección que han vivido, hablemos de por qué existen estas particiones.
Colección Gen 0
Siendo Gen 0 los objetos más nuevos, tiende a ser muy pequeño, por lo que puede recolectarlo con seguridad con mucha frecuencia . La frecuencia garantiza que el montón se mantenga pequeño y las colecciones sean muy rápidas porque se están acumulando en un montón tan pequeño. Esto se basa más o menos en una heurística que afirma: una gran mayoría de los objetos temporales que crea son muy temporales, por lo que ya no se usarán ni se referenciarán casi inmediatamente después del uso, por lo que se pueden recolectar.
Colección Gen 1
Gen 1, siendo objetos que no cayeron en esta categoría muy temporal de objetos, aún puede ser de corta duración, porque aún así, una gran parte de los objetos creados no se usan por mucho tiempo. Por lo tanto, Gen 1 también recolecta con bastante frecuencia, nuevamente manteniendo su montón pequeño para que sus colecciones sean rápidas. Sin embargo, la suposición es menos de TI de objetos que son temporales Gen 0, por lo que se acumula con menos frecuencia que Gen 0
Diré francamente que no conozco los mecanismos técnicos que difieren entre el pase de recolección de Gen 0 y el Gen 1, si hay alguno que no sea la frecuencia que recolectan.
Colección Gen 2
Gen 2 ahora debe ser la madre de todos los montones, ¿verdad? Bueno, sí, eso es más o menos correcto. Es donde viven todos sus objetos permanentes: el objeto en el que Main()
vive, por ejemplo, y todo lo que hace Main()
referencia porque se arraigará hasta que Main()
regrese al final de su proceso.
Dado que Gen 2 es un cubo para básicamente todo lo que las otras generaciones no pudieron recolectar, sus objetos son en gran medida permanentes, o al menos de larga duración. Por lo tanto, reconocer muy poco de lo que hay en Gen 2 en realidad será algo que se puede recopilar, no es necesario que se recopile con frecuencia. Esto permite que su colección también sea más lenta, ya que se ejecuta con mucha menos frecuencia. Entonces, esto es básicamente donde han agregado todos los comportamientos adicionales para escenarios extraños, porque tienen tiempo para ejecutarlos.
Montón de objetos grandes
Un ejemplo de los comportamientos adicionales de Gen 2 es que también realiza la recopilación en el Montón de objetos grandes. Hasta ahora he estado hablando completamente sobre el montón de objetos pequeños, pero el tiempo de ejecución de .NET asigna cosas de ciertos tamaños a un montón separado debido a lo que denominé compactación anterior. La compactación requiere mover objetos cuando las colecciones terminan en el montón de objetos pequeños. Si hay un objeto vivo de 10 MB en Gen 1, le llevará mucho más tiempo completar la compactación después de la colección, lo que ralentizará la colección de Gen 1. De modo que ese objeto de 10mb se asigna al Montón de objetos grandes y se recopila durante Gen 2, que se ejecuta con poca frecuencia.
Finalizacion
Otro ejemplo son los objetos con finalizadores. Coloca un finalizador en un objeto que hace referencia a recursos más allá del alcance de .NETs GC (recursos no administrados). El finalizador es la única forma en que el GC puede exigir que se recopile un recurso no administrado: usted implementa su finalizador para realizar la recolección / eliminación / liberación manual del recurso no administrado para garantizar que no se filtre en su proceso. Cuando el GC llega a ejecutar su finalizador de objetos, su implementación borrará el recurso no administrado, haciendo que el GC sea capaz de eliminar su objeto sin correr el riesgo de una fuga de recursos.
El mecanismo con el que los finalizadores hacen esto es mediante referencia directa en una cola de finalización. Cuando el tiempo de ejecución asigna un objeto con un finalizador, agrega un puntero a ese objeto a la cola de finalización y bloquea su objeto en su lugar (llamado fijación) para que la compactación no lo mueva, lo que rompería la referencia de la cola de finalización. A medida que ocurren los pases de recopilación, eventualmente se descubrirá que su objeto ya no tiene una raíz GC, pero la finalización debe ejecutarse antes de que pueda recopilarse. Por lo tanto, cuando el objeto está muerto, la colección moverá su referencia de la cola de finalización y colocará una referencia en lo que se conoce como la cola "FReachable". Luego la colección continúa. En otro momento "no determinista" en el futuro, un hilo separado conocido como el hilo Finalizador pasará por la cola FReachable, ejecutando los finalizadores para cada uno de los objetos referenciados. Una vez finalizado, la cola FReachable está vacía y se ha volteado un poco en el encabezado de cada objeto que dice que no necesitan finalización (este bit también se puede voltear manualmente conGC.SuppressFinalize
que es común en los Dispose()
métodos), también sospecho que ha desanclado los objetos, pero no me cite al respecto. La próxima colección que aparezca en cualquier montón en el que se encuentre este objeto, finalmente la recopilará. Las colecciones Gen 0 ni siquiera prestan atención a los objetos con ese bit de finalización necesario, los promueve automáticamente, sin siquiera verificar su raíz. Un objeto no enraizado que necesita finalización en Gen 1, será arrojado a la FReachable
cola, pero la colección no hace nada más con él, por lo que vive en Gen 2. De esta manera, todos los objetos que tienen un finalizador, y no GC.SuppressFinalize
será recolectado en Gen 2.