Algunos recolectores de basura (al menos Mono y .NET) tienen un área de memoria a corto plazo que escanean con frecuencia y un área de memoria secundaria que escanean con menos frecuencia. Mono llama a esto una guardería.
Para averiguar qué objetos se pueden eliminar, escanean todos los objetos a partir de las raíces, la pila y los registros, y eliminan todos los objetos a los que ya no se hace referencia.
Mi pregunta es cómo evitan que se escanee toda la memoria en uso en cada recopilación. En principio, la única forma de averiguar qué objetos ya no se usan es escanear todos los objetos y todas sus referencias. Sin embargo, esto evitaría que el sistema operativo intercambie memoria a pesar de que la aplicación no lo utiliza y se siente como una gran cantidad de trabajo que debe hacerse, también para "Nursery Collection". No parece que estén ganando mucho al usar una guardería.
¿Me estoy perdiendo algo o el recolector de basura realmente escanea cada objeto y cada referencia cada vez que hace una recolección?
fuente
Respuestas:
Las observaciones fundamentales que permiten que la recolección de basura generacional evite tener que escanear todos los objetos de generaciones anteriores son:
En muchos marcos de trabajo de GC, es posible que el recolector de basura marque objetos o partes de ellos de tal manera que el primer intento de escribir en ellos active un código especial para registrar el hecho de que se han modificado. Un objeto o parte del mismo que ha sido modificado, independientemente de su generación, debe escanearse en la próxima colección, ya que puede contener referencias a objetos más nuevos. Por otro lado, es muy común que haya muchos objetos antiguos que no se modifiquen entre colecciones. El hecho de que los escaneos de baja generación puedan ignorar tales objetos puede permitir que dichos escaneos se completen mucho más rápidamente de lo que lo harían de otra manera.
Tenga en cuenta, por cierto, que incluso si uno no puede detectar cuándo se modifican los objetos y tendría que escanear todo en cada paso del GC, la recolección de basura generacional aún podría mejorar el rendimiento de la etapa de "barrido" de un recolector de compactación. En algunos entornos integrados (especialmente aquellos en los que hay poca o ninguna diferencia de velocidad entre los accesos de memoria secuenciales y aleatorios), mover bloques de memoria es relativamente costoso en comparación con las referencias de etiquetado. En consecuencia, incluso si la fase de "marca" no se puede acelerar utilizando un colector generacional, puede valer la pena acelerar la fase de "barrido".
fuente
Los GC a los que se refiere son recolectores de basura generacionales . Están diseñados para aprovechar al máximo una observación conocida como "mortalidad infantil" o "hipótesis generacional", lo que significa que la mayoría de los objetos se vuelven inalcanzables muy rápidamente. De hecho, escanean a partir de las raíces, pero ignoran todos los objetos antiguos . Por lo tanto, no necesitan escanear la mayoría de los objetos en la memoria, solo escanean objetos jóvenes (a expensas de no detectar objetos viejos inalcanzables, al menos no en ese punto).
"Pero eso está mal", te escucho gritar, "los objetos viejos pueden referirse y se refieren a objetos jóvenes". Tiene razón, y hay varias soluciones para eso, que giran en torno a la obtención de conocimiento, de manera rápida y eficiente, qué objetos antiguos deben verificarse y cuáles son seguros de ignorar. Se reducen prácticamente a objetos de grabación, o pequeños rangos de memoria (más grandes que los objetos, pero mucho más pequeños que todo el montón) que contienen punteros a las generaciones más jóvenes. Otros lo han descrito mucho mejor que yo, así que solo le daré un par de palabras clave: marcado de tarjetas, conjuntos recordados, barreras de escritura. También hay otras técnicas (incluidos los híbridos), pero estas abarcan los enfoques comunes que conozco.
fuente
Para averiguar qué objetos de guardería aún están vivos, el recolector solo necesita escanear el conjunto raíz y cualquier objeto antiguo que haya sido mutado desde la última colección , ya que un objeto antiguo que no ha sido mutado recientemente no puede apuntar a un objeto joven . Existen diferentes algoritmos para mantener esta información en diferentes niveles de precisión (desde un conjunto exacto de campos mutados a un conjunto de páginas donde puede haber ocurrido una mutación), pero todos generalmente involucran algún tipo de barrera de escritura : código que se ejecuta en cada referencia de tipo de campo que actualiza la contabilidad del GC.
fuente
La generación más antigua y más simple de recolectores de basura realmente escaneó toda la memoria y tuvo que detener el resto del procesamiento mientras lo hacían. Los algoritmos posteriores mejoraron esto de varias maneras, haciendo que la copia / escaneo sea incremental o se ejecute en paralelo. La mayoría de los recolectores de basura modernos segregan los objetos en generaciones, y manejan cuidadosamente los punteros intergeneracionales para que las nuevas generaciones puedan ser recolectadas sin molestar a las más antiguas.
El punto clave es que los recolectores de basura trabajan en estrecha colaboración con el compilador y con el resto del tiempo de ejecución para mantener la ilusión de que está observando toda la memoria.
fuente
Básicamente ... GC usa "cubos" para separar lo que está en uso y lo que no. Una vez que lo hace, borra las cosas que no están en uso y mueve todo lo demás a la 2da generación (que se verifica con menos frecuencia que la 1ra generación) y luego mueve las cosas que todavía están en uso en la 2da den a la 3ra generación.
Entonces, las cosas en la tercera generación generalmente son objetos que están atascados por alguna razón, y GC no revisa allí muy a menudo.
fuente
El algoritmo generalmente utilizado por este GC es el marcado y barrido Naive
También debe tener en cuenta el hecho de que esto no es administrado por el C # en sí, sino por el llamado CLR .
fuente