¿Y si te dijera que a partir de .NET 10, varias de tus ideas fundamentales sobre la recolección de basura ya están obsoletas? Imagina que existen mejoras reales que pueden proporcionar hasta dos o tres veces mejor uso de memoria y velocidad. Estas mejoras están disponibles a través de una serie de configuraciones de runtime y nuevos comportamientos de optimización. Sin embargo, es importante considerar que estas mejoras vienen con compromisos que necesitas evaluar en lugar de simplemente habilitarlas a ciegas.
En este artículo, te llevaré a través de la historia real en .NET 10, te mostraré la lógica detrás de las nuevas características del GC, te daré patrones accionables, código y herramientas de medición, y te ayudaré a responder: ¿deberías confiar en estas mejoras o ajustarlas e incluso deshabilitarlas para tu escenario?
Fundamentos de la recolección de basura en .NET
Desde los inicios del CLR, el modelo de gestión de memoria de .NET ha utilizado un recolector de basura generacional y de seguimiento. Este modelo significa que todas las asignaciones de objetos viven en un heap administrado, con el GC rastreando qué objetos siguen «en uso» (accesibles desde las raíces de la aplicación) y cuáles pueden ser recuperados.
El GC divide la memoria del heap en:
- Generación 0: los objetos más jóvenes, recolectados con mayor frecuencia
- Generación 1: supervivientes promovidos desde Gen 0, actuando como buffer
- Generación 2: supervivientes de larga duración—piensa en cachés, estáticos, o modelos persistentes
- Large Object Heap (LOH/»Gen 3″): para objetos >85 KB, manejados especialmente para evitar compactación frecuente
¿Por qué generaciones?
Porque la mayoría de los objetos mueren jóvenes. Enfocar las recolecciones en Gen 0 significa menor overhead, menos pausas de heap completo y mejor localidad de caché.
Fases de recolección del GC
Cada ciclo de GC ejecuta tres pasos amplios:
- Marcar objetos vivos comenzando desde raíces conocidas
- Reubicar referencias si los objetos pueden ser movidos
- Compactar memoria deslizando/moviendo objetos vivos, reduciendo fragmentación
Modos de GC: Workstation vs Server GC
- Workstation GC: Predeterminado para aplicaciones de escritorio. Diseñado para capacidad de respuesta de UI usando hilos mínimos y recolección en segundo plano
- Server GC: Diseñado para servicios front/back-end. Paraleliza la recolección a través de múltiples heaps/cores, maximizando el throughput
La configuración es un único flag de runtime:
{ "runtimeOptions": { "configProperties": { "System.GC.Server": true } } }
Siempre haz benchmarks con el modo GC destinado para tu despliegue. El modo incorrecto a menudo causa latencia inesperada.
La evolución del GC en .NET
Antes de diseccionar lo nuevo en .NET 10, recordemos la historia hasta ahora. Cada release principal de .NET ha empujado el rendimiento del GC y la flexibilidad del desarrollador hacia adelante:
- .NET Framework Era: Introdujo modelos GC generacionales, workstation/server, recolección en segundo plano y LOH (no compactado por defecto)
- .NET Core (1-3): Runtime modularizado, GC verdaderamente multiplataforma y escalabilidad mejorada server por hilo/por core
- .NET Core 3.1–6: Compactación LOH bajo demanda, GCHeapHardLimit para contenedores/nube y soporte para configuración GC más fina
- .NET 7-9: Gestión de heap basada en regiones y DATAS (Dynamic Adaptation To Application Sizes), auto-ajustando uso de heap basado en comportamiento de app (especialmente en contenedores)
- .NET 10: Grandes saltos en análisis de escape (para asignación de stack), optimización de delegates, dimensionamiento de regiones y DATAS ahora habilitado por defecto