Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La administración automática de memoria es uno de los servicios que Common Language Runtime proporciona durante la ejecución administrada. El recolector de elementos no utilizados de Common Language Runtime administra la asignación y liberación de memoria de una aplicación. Para los desarrolladores, esto significa que no es necesario escribir código para realizar tareas de administración de memoria al desarrollar aplicaciones administradas. La administración automática de memoria puede eliminar problemas comunes, como olvidar liberar un objeto y provocar una pérdida de memoria, o intentar acceder a la memoria de un objeto que ya se ha liberado. En esta sección se describe cómo el recolector de basura asigna y libera memoria.
Asignar memoria
Al inicializar un nuevo proceso, el entorno de ejecución reserva una región contigua del espacio de direcciones para el proceso. Este espacio de direcciones reservado se denomina montón administrado. El montón administrado mantiene un puntero a la dirección a la que se asignará el siguiente objeto del montón. Inicialmente, este puntero se establece en la dirección base del montón administrado. Todos los tipos de referencia se asignan en el montón administrado. Cuando una aplicación crea el primer tipo de referencia, se le asigna memoria en la dirección base del montón administrado. Cuando la aplicación crea el siguiente objeto, el recolector de elementos no utilizados asigna memoria para él en el espacio de direcciones inmediatamente después del primer objeto. Siempre que haya espacio de direcciones disponible, el recolector de elementos no utilizados continúa asignando espacio a los objetos nuevos de este modo.
Asignar memoria del montón administrado es más rápido que la asignación de memoria no administrada. Dado que el entorno de ejecución asigna memoria para un objeto agregando un valor a un puntero, es casi tan rápido como asignar memoria desde la pila. Además, dado que los nuevos objetos asignados consecutivamente se almacenan de forma contigua en el montón administrado, una aplicación puede acceder a los objetos muy rápidamente.
Liberar memoria
El motor de optimización del recolector de basura determina el mejor momento para realizar una recolección basándose en las asignaciones realizadas. Cuando el recolector de elementos no utilizados realiza una recolección, libera la memoria de los objetos que la aplicación ya no usa. Determina qué objetos ya no se usan examinando las raíces de la aplicación. Cada aplicación tiene un conjunto de raíces. Cada raíz hace referencia a un objeto del montón administrado o se establece en NULL. Las raíces de una aplicación incluyen campos estáticos, variables locales y parámetros en la pila de un subproceso y registros de CPU. El recolector de elementos no utilizados tiene acceso a la lista de raíces activas que el compilador Just-In-Time (JIT) y el runtime mantienen. Con esta lista, examina las raíces de una aplicación y, en el proceso, crea un gráfico que contiene todos los objetos accesibles desde las raíces.
Los objetos que no están en el grafo no son accesibles desde las raíces de la aplicación. El colector de basura considera como basura los objetos inaccesibles y liberará la memoria asignada para ellos. Durante una recolección, el recolector de elementos no utilizados examina el montón administrado y busca los bloques de espacio de direcciones que ocupan los objetos que no se pueden alcanzar. A medida que detecta cada objeto inaccesible, usa una función de copia de memoria para compactar los objetos accesibles en la memoria, liberando los bloques de espacios de direcciones asignados a objetos inaccesibles. Una vez que se ha compactado la memoria de los objetos alcanzables, el recolector de elementos no utilizados hace las correcciones de puntero necesarias para que las raíces de la aplicación señalen a los objetos en sus nuevas ubicaciones. También sitúa el puntero del montón administrado después del último objeto alcanzable. Tenga en cuenta que la memoria solo se compacta si una colección detecta un número significativo de objetos inaccesibles. Si todos los objetos del montón administrado sobreviven a una recolección, no hay necesidad de comprimir la memoria.
Para mejorar el rendimiento, el tiempo de ejecución asigna memoria a los objetos grandes en un montón aparte. El recolector de elementos no utilizados libera la memoria para los objetos grandes automáticamente. Sin embargo, para evitar mover objetos grandes en memoria, esta memoria no se compacta.
Generaciones y rendimiento
Para optimizar el rendimiento del recolector de basura, el montón gestionado se divide en tres generaciones: 0, 1 y 2. El algoritmo de recolección de basura del entorno de ejecución se basa en varias generalizaciones que la industria del software ha descubierto que son verdaderas al experimentar con esquemas de recolección de basura. Primero, es más rápido compactar la memoria de una parte del montón administrado que la de todo el montón. En segundo lugar, los objetos más recientes tendrán una duración más corta y los objetos más antiguos tendrán más duración. Por último, los objetos más recientes tienden a estar relacionados entre sí y a los que la aplicación accede aproximadamente al mismo tiempo.
El recolector de elementos no utilizados del entorno de ejecución almacena nuevos objetos en la generación 0. Los objetos creados al principio de la vigencia de la aplicación que sobreviven a las colecciones se promueven y almacenan en las generaciones 1 y 2. El proceso de promoción de objetos se describe más adelante en este tema. Como es más rápido compactar una parte del montón administrado que todo el montón, este esquema permite que el recolector de elementos no utilizados libere la memoria en una generación específica en lugar de liberarla para todo el montón administrado cada vez que realiza una recolección.
En realidad, el recolector de basura realiza una recolección cuando la generación 0 está llena. Si una aplicación intenta crear un nuevo objeto cuando la generación 0 está llena, el recolector de elementos no utilizados detecta que no hay espacio de direcciones restante en la generación 0 para asignar para el objeto. El recolector de elementos no utilizados realiza una recolección, en un intento de liberar espacio de direcciones para el objeto en la generación 0. El recolector de elementos no utilizados comienza examinando los objetos de la generación 0 en lugar de todos los objetos del montón administrado. Este es el enfoque más eficaz, ya que los nuevos objetos tienden a tener una duración corta y se espera que la aplicación deje de usar muchos de los objetos de la generación 0 cuando se realiza una colección. Además, una colección de generación 0 por sí sola a menudo recupera suficiente memoria para permitir que la aplicación continúe creando nuevos objetos.
Después de que el recolector de basura realice una colección de la generación 0, compacta la memoria de los objetos alcanzables, como se explica en Liberar memoria anteriormente en este tema. A continuación, el recolector de elementos no utilizados promueve estos objetos y considera que esta parte del montón administrado está en la generación 1. Dado que los objetos que sobreviven a las colecciones tienden a tener duraciones más largas, tiene sentido promoverlas a una generación superior. Como resultado, el recolector de elementos no utilizados no tiene que volver a examinar los objetos de las generaciones 1 y 2 cada vez que realiza una recolección de generación 0.
Una vez que el recolector de elementos no utilizados realiza su primera recolección de la generación 0 y promueve los objetos que se pueden alcanzar a la generación 1, considera que lo que queda del montón administrado forma parte de la generación 0. Continúa asignando memoria para los nuevos objetos de la generación 0 hasta que la generación 0 está llena y es necesario realizar otra colección. En este momento, el motor de optimización del recolector de basura determina si es necesario examinar los objetos de generaciones más antiguas. Por ejemplo, si una colección de generación 0 no reclama suficiente memoria para que la aplicación complete correctamente su intento de crear un nuevo objeto, el recolector de elementos no utilizados puede realizar una colección de generación 1 y, a continuación, la generación 2. Si esto no libera suficiente memoria, el recolector de basura podría realizar una recolección de generaciones 2, 1 y 0. Después de cada recolección, el recolector de basura compacta los objetos accesibles en la generación 0 y los promueve a la generación 1. Los objetos de la generación 1 que sobreviven a las colecciones se promueven a la generación 2. Dado que el recolector de basura solo admite tres generaciones, los objetos de la generación 2 que sobreviven a una colección permanecen en la generación 2 hasta que se determina que no son accesibles en una colección futura.
Liberar memoria para recursos no administrados
Para la mayoría de los objetos que crea la aplicación, puede confiar en que el recolector de elementos no utilizados realice automáticamente las tareas de administración de memoria necesarias. Sin embargo, los recursos no administrados requieren una limpieza explícita. El tipo más común de recurso no administrado es un objeto que encapsula un recurso del sistema operativo, como un identificador de archivo, un identificador de ventana o una conexión de red. Aunque el recolector de elementos no utilizados puede realizar un seguimiento de la duración de un objeto administrado que encapsula un recurso no administrado, no tiene conocimientos específicos sobre cómo limpiar el recurso. Al crear un objeto que encapsula un recurso no administrado, se recomienda proporcionar el código necesario para limpiar el recurso no administrado en un método Dispose público. Al proporcionar un método Dispose , permite que los usuarios del objeto liberen explícitamente su memoria cuando terminen con el objeto . Si se utiliza un objeto que encapsula un recurso no administrado, se debe conocer la existencia de Dispose y llamarlo cuando sea necesario. Para obtener más información sobre cómo limpiar recursos no administrados y un ejemplo de un patrón de diseño para implementar Dispose, vea Recolección de elementos no utilizados.