Diré de inmediato: nunca espero una respuesta detallada a esta pregunta sobre la seguridad social. Esto es estúpido y en mi caso egoísta. Sin embargo, en mi opinión, además del interés general en la plataforma, es muy útil saber cómo funciona, porque Esto elimina una serie de problemas. Por ejemplo, excluye la opción cuando el desarrollador cree que Dispose
se llama automáticamente y no necesita llamarlo usted mismo. O si el desarrollador tiene más experiencia, lo ayuda automáticamente, a nivel de memoria muscular, a escribir el código que conduce a la menor cantidad de problemas.
Otra pregunta que subjetivamente no me gusta es cómo se explica su trabajo. Por lo tanto, propongo un enfoque alternativo descrito en mi libro, .NET Platform Architecture .
Si vamos a comprender a fondo por qué se eligieron estos dos algoritmos de administración de memoria: Barrido y Compacto, tendremos que considerar docenas de algoritmos de administración de memoria que existen en el mundo: comenzando con diccionarios ordinarios y terminando con estructuras muy complejas sin bloqueo. En cambio, dejando nuestros pensamientos sobre lo que es útil, simplemente justificamos la elección y, por lo tanto, entendemos por qué la elección se hizo de esa manera. Ya no miramos el folleto de lanzamiento de refuerzo: tenemos en nuestras manos un conjunto completo de documentación.
La disputa es mutuamente beneficiosa: si no está clara, corregiré los puntos poco claros del libro , una pequeña parte del cual es el texto dado.

Elegí el formato del argumento para que sientas que los arquitectos de la plataforma y yo hemos llegado a las mismas conclusiones a las que llegaron los verdaderos arquitectos en la sede de Microsoft en Redmond.
En función de la clasificación de los objetos asignados en función de su tamaño, puede dividir el espacio para la asignación de memoria en dos grandes secciones: un lugar con objetos de tamaño inferior a un cierto umbral y un lugar con un tamaño superior a este umbral y ver qué diferencia se puede hacer en la gestión de estos grupos (según su tamaño) y lo que viene de ello.
Si consideramos la gestión de objetos " pequeños " convencionalmente, podemos ver que si nos adherimos a la idea de almacenar información sobre cada objeto, nos resultará muy costoso mantener estructuras de datos de gestión de memoria que almacenarán enlaces a cada uno de esos objetos. Al final, puede resultar que para almacenar información sobre un objeto, necesitará tanta memoria como el objeto en sí. En cambio, debe considerar: si durante la recolección de basura bailamos desde las raíces, profundizando en el gráfico a través de los campos salientes del objeto, y necesitamos un pasaje lineal a lo largo del montón solo para identificar objetos basura, ¿es necesario que almacenemos información sobre cada objeto en los algoritmos de administración de memoria? La respuesta es obvia: no hay necesidad de esto. Por lo tanto, podemos tratar de proceder del hecho de que no debemos almacenar dicha información: podemos pasar por un grupo linealmente, conociendo el tamaño de cada objeto y moviendo el puntero cada vez por el tamaño del siguiente objeto.
No hay estructuras de datos adicionales en el montón que mantienen punteros a cada objeto que controla el montón.
Sin embargo, cuando ya no necesitemos memoria, debemos liberarla. Y al liberar memoria, nos resulta difícil confiar en el paso lineal del montón: es largo y no es efectivo. Como resultado, llegamos a la conclusión de que necesitamos de alguna manera almacenar información sobre áreas de memoria libre.
El montón tiene listas de memoria libre.
Si, como decidimos, almacenar información sobre áreas libres, y al tiempo que liberamos memoria, estas áreas eran demasiado pequeñas, entonces, ante todo, llegamos al mismo problema de almacenar información sobre áreas libres que encontramos al considerar las ocupadas (si en los lados del objeto ocupado se liberó, para almacenar información sobre el mismo, es necesario en el peor de los casos 2/3 de su tamaño. Puntero + tamaño versus SyncBlockIndex + VMT + algún campo - en el caso del objeto). Esto nuevamente suena un desperdicio, debes admitir: no siempre es buena suerte liberar un grupo de objetos que se siguen. Por lo general, se liberan de manera caótica. Pero a diferencia de los sitios ocupados, que no necesitamos buscar linealmente, necesitamos buscar sitios gratuitos porque cuando asignamos memoria, es posible que los necesitemos nuevamente. Por lo tanto, surge un deseo completamente natural de reducir la fragmentación y exprimir el montón, moviendo todas las áreas ocupadas a lugares libres, formando así un área grande del área libre donde se puede asignar memoria.
De aquí nace la idea del algoritmo de compactación.
Pero espera, dices. Después de todo, esta operación puede ser muy difícil. Imagina que liberaste un objeto al principio del montón. ¿Y qué, dices, necesitas mover todo? Bueno, por supuesto, puedes soñar con el tema de las instrucciones vectoriales de la CPU, que puedes usar para copiar una gran área ocupada de memoria. Pero esto es solo el comienzo del trabajo. También debemos fijar todos los punteros desde los campos de objetos a los objetos que han sufrido movimientos. Esta operación puede llevar mucho tiempo. No, debemos proceder de otra cosa. Por ejemplo, dividiendo todo el segmento de la memoria de almacenamiento dinámico en sectores y trabajando con ellos por separado. Si trabajamos por separado en cada sector (para la previsibilidad y la escalabilidad de esta previsibilidad, preferiblemente, tamaños fijos), la idea de compresión no parece tan pesada: es suficiente para comprimir un solo sector e incluso puede comenzar a hablar sobre el tiempo que lleva comprimir uno de esos sectores .
Ahora queda por entender sobre qué base dividir en sectores. Aquí debemos pasar a la segunda clasificación, que se introduce en la plataforma: el intercambio de memoria, basado en el tiempo de vida de sus elementos individuales.
La división es simple: si tenemos en cuenta que asignaremos memoria a medida que aumenten las direcciones, los primeros objetos seleccionados se convertirán en los más antiguos y los que estén en las direcciones principales se convertirán en los más jóvenes. Además, al ser inteligente, puede llegar a la conclusión de que en las aplicaciones los objetos se dividen en dos grupos: aquellos que fueron creados para una larga vida y aquellos que fueron creados para vivir muy poco. Por ejemplo, para almacenar temporalmente punteros a otros objetos en forma de colección. O los mismos objetos DTO. En consecuencia, de vez en cuando, al apretar un montón, obtenemos una serie de objetos de larga vida, en las direcciones más bajas y varios de corta duración, en las personas mayores.
Así hemos recibido generaciones .
Dividiendo la memoria en generaciones, tenemos la oportunidad de mirar con menos frecuencia los objetos de la generación anterior, que se están volviendo cada vez más.
Pero surge otra pregunta: si solo tenemos dos generaciones, tendremos problemas. O intentaremos hacer que GC funcione sin enmascaramiento rápido: luego, el tamaño de la generación más joven, intentaremos hacer el tamaño mínimo. Como resultado, los objetos fallarán accidentalmente en la generación anterior (si el GC funcionó "en este momento, durante una asignación furiosa de memoria para muchos objetos"). O, para minimizar la falla accidental, aumentaremos el tamaño de la generación más joven. Entonces, el GC en la generación más joven funcionará lo suficiente, ralentizando y ralentizando la aplicación.
La salida es la introducción de la generación "media". Adolescente En otras palabras, si vive hasta la adolescencia, es más probable que viva hasta la vejez. La esencia de su introducción es lograr un equilibrio entre obtener la generación más joven y más pequeña y la generación más estable más antigua , donde es mejor no tocar nada. Esta es una zona donde el destino de los objetos aún no se ha decidido. La primera generación (no olvidemos lo que pensamos desde cero) también se crea pequeña y GC se ve con menos frecuencia allí. Por lo tanto, GC permite que los objetos que están en la primera generación temporal no entren en la generación anterior, lo cual es extremadamente difícil de recolectar.
Entonces tenemos la idea de tres generaciones.
La siguiente capa de optimización es un intento de rechazar la compresión. Después de todo, si no lo haces, nos deshacemos de una gran capa de trabajo. Volvamos al tema de los sitios gratuitos.
Si después de haber utilizado toda la memoria disponible en el montón y se ha llamado a GC, existe un deseo natural de rechazar la compresión a favor de asignar más memoria dentro de las secciones liberadas si su tamaño es suficiente para acomodar un cierto número de objetos. Aquí llegamos a la idea de un segundo algoritmo para liberar memoria en el GC, llamado Sweep
: no comprimimos memoria, usamos vacíos de objetos liberados para colocar nuevos objetos
Así que describimos y justificamos todos los conceptos básicos de los algoritmos de GC.
Entonces, después de dos días, podemos sacar algunas conclusiones. Según tengo entendido, la mayoría de las personas entienden la mayor parte del texto, o incluso la totalidad. Algunas personas respondieron que no entendían, algunas, respectivamente, entendieron parcialmente. La disputa fue ganada por un equipo de lectores, aunque con un ligero margen, como dicen. Pero, como dije, todos se beneficiarán de esto: el texto será enmendado y complementado. Además, actualizado en ambos lugares: tanto en el libro como aquí, en el artículo.
