Estaba perfilando el uso de memoria de una aplicación de Windows Forms en dotmemory y noté que para mi aplicación había 0-4 montones, todos de diferentes tamaños, así como el montón de objetos grandes.
Me preguntaba si alguien tenía una buena explicación de para qué sirve cada montón y qué se almacena normalmente en cada montón.
Las otras respuestas parecen no tener en cuenta el hecho de que existe una diferencia entre montones y generaciones . No veo por qué un perfilador comercial confundiría los dos conceptos, por lo que sospecho que son montones y no generaciones después de todo.
Cuando CLR GC usa el tipo de servidor , crea un montón separado para cada procesador lógico en la máscara de afinidad del proceso. El motivo de este desglose es principalmente para mejorar la escalabilidad de las asignaciones y para funcionar en GC en paralelo. Estas son regiones de memoria separadas, pero, por supuesto, puede tener referencias de objetos entre los montones y puede considerarlos como un único montón lógico.
Entonces, suponiendo que tiene cuatro procesadores lógicos (por ejemplo, una CPU i5 con HyperThreading habilitado), tendrá cuatro montones en el servidor GC.
El montón de objetos grandes tiene un nombre desafortunado y confuso. No es un montón en el mismo sentido que los montones por procesador. Es una abstracción lógica sobre múltiples regiones de memoria que contienen objetos grandes.
Tiene montones diferentes debido a cómo funciona el recolector de elementos no utilizados de C#. Utiliza un GC generacional, que separa los datos en función de la fecha de uso. El uso de montones diferentes permite que el recolector de basura limpie la memoria de manera más eficiente.
Según MSDN :
El montón está organizado en generaciones para que pueda manejar objetos de larga y corta duración. La recolección de elementos no utilizados se produce principalmente con la recuperación de objetos de corta duración que, por lo general, ocupan solo una pequeña parte del almacenamiento dinámico.
- Generación 0. Esta es la generación más joven y contiene objetos de corta duración. Un ejemplo de un objeto de corta duración es una variable temporal. La recolección de basura ocurre con mayor frecuencia en esta generación. Los objetos recién asignados forman una nueva generación de objetos y son implícitamente colecciones de generación 0, a menos que sean objetos grandes, en cuyo caso van al montón de objetos grandes en una colección de generación 2. La mayoría de los objetos se reclaman para la recolección de basura en la generación 0 y no sobreviven a la próxima generación.
- Generación 1. Esta generación contiene objetos de vida corta y sirve como amortiguador entre los objetos de vida corta y los objetos de vida larga.
- Generación 2. Esta generación contiene objetos de larga vida. Un ejemplo de un objeto de larga duración es un objeto en una aplicación de servidor que contiene datos estáticos que están activos durante la duración del proceso.
Los objetos que no se reclaman en una recolección de elementos no utilizados se conocen como sobrevivientes y se promocionan a la próxima generación.
Los datos importantes se colocan rápidamente en el segundo plano del recolector de basura (generaciones superiores) y se verifican para eliminarlos con menos frecuencia. Esto reduce la cantidad de tiempo que se desperdicia comprobando la memoria que realmente necesita persistir, lo que le permite ver las ganancias de rendimiento de un recolector de elementos no utilizados eficiente.
Cuando se trata de objetos administrados, hay tres pilas de objetos pequeños (SOH) y una pila de objetos grandes (LOH).
Montón de objetos grandes (LOH)
Los objetos que tienen más de 85 KB van a LOH de inmediato. Existen algunos riesgos si tiene demasiados objetos grandes. Esa es una discusión diferente, para obtener más detalles, eche un vistazo a Los peligros del montón de objetos grandes
Montón de objetos pequeños (SOH): Gen0, Gen1, Gen2
El recolector de basura utiliza un algoritmo inteligente para ejecutar el recolector de basura solo cuando es necesario. El proceso completo de recolección de basura es una operación costosa que no debería ocurrir con demasiada frecuencia. Entonces, ha dividido su SOH en tres partes y, como habrá notado, cada Gen tiene una cantidad específica de memoria.
Cada objeto pequeño (<85 KB) inicialmente va a Gen0. Cuando Gen0 está lleno, la recolección de elementos no utilizados se ejecuta solo para Gen0. Comprueba todas las instancias que están en Gen0 y borra/libera la memoria que utilizan los objetos innecesarios (objetos no referenciados, fuera del alcance o desechados). Y luego copia todas las instancias requeridas (en uso) a Gen1.
El proceso anterior en realidad ocurre incluso cuando ejecuta a continuación: (no es necesario llamar manualmente)
// Perform a collection of generation 0 only. GC.Collect(0);De esta manera, el recolector de basura borra primero la memoria que se asigna para instancias de corta duración (cadenas que son inmutables, variables en métodos o ámbitos más pequeños).
Cuando GC continúa realizando esta operación en una etapa, Gen1 se desborda. Luego hace la misma operación a Gen1. Borra toda la memoria innecesaria en Gen1 y copia todas las requeridas en Gen2.
El proceso anterior se produce cuando ejecuta a continuación manualmente (no es necesario llamar manualmente)
// Perform a collection of all generations up to and including 1. GC.Collect(1);Cuando el GC continúa realizando esta operación en una etapa, si Gen2 se desborda, intenta limpiar Gen2.
El proceso anterior ocurre incluso cuando ejecuta a continuación manualmente (no es necesario hacerlo manualmente)
// Perform a collection of all generations up to and including 2. GC.Collect(2);Si la cantidad de memoria que se necesita copiar de Gen1 a Gen2 es mayor que la cantidad de memoria disponible en Gen2, GC arroja una excepción de memoria insuficiente.