La gestión de la memoria es un componente crucial del desarrollo de software informático, encargado de la asignación, utilización y liberación efectiva de la memoria en las aplicaciones. Su importancia radica en mejorar el rendimiento del software y garantizar la estabilidad del sistema.
La recolección de basura (GC) es fundamental en los lenguajes de programación contemporáneos como Java y Go. Detecta y recicla de forma autónoma la memoria no utilizada, aliviando así la necesidad de que los desarrolladores administren la memoria manualmente. El concepto de GC surgió originalmente en el lenguaje de programación LISP a finales de la década de 1950, lo que marcó la introducción de la gestión de memoria automatizada.
Las ventajas clave de la gestión automatizada de la memoria incluyen:
Comprender la naturaleza de la "basura" en la memoria e identificar el espacio recuperable es esencial. En los próximos capítulos, comenzaremos explorando los principios fundamentales de la recolección de basura.
El algoritmo de recuento de referencias asigna un campo en el encabezado del objeto para realizar un seguimiento de su recuento de referencias. Este recuento aumenta con cada nueva referencia y disminuye cuando se elimina una referencia. Cuando el recuento llega a cero, el objeto es elegible para la recolección de basura.
Considere el siguiente código:
Primero cree una cadena con valor de demostración a la que hace referencia d (Figura 1).
String d = new String("demo");
Figura 1: después de crear una cadena
Luego, establezca d en nulo. El recuento de referencias de la demostración es cero. En el algoritmo de conteo de referencias, la memoria para la demostración debe recuperarse (Figura 2).
d =null; // Reference count of 'demo' becomes zero, prompting garbage collection.
Figura 2 – Cuando se anula la referencia
El algoritmo de conteo de referencias opera durante la ejecución del programa, evitando eventos Stop-The-World, que detienen temporalmente el programa para la recolección de basura. Sin embargo, su principal inconveniente es la incapacidad de manejar referencias circulares (Figura 3).
Por ejemplo:
public class CircularReferenceDemo { public CircularReferenceDemo reference; private String name; public CircularReferenceDemo(String name) { this.name = name; } public void setReference(CircularReferenceDemo ref) { this.reference = ref; } public static void main(String[] args) { CircularReferenceDemo objA = new CircularReferenceDemo("Ref_A"); CircularReferenceDemo objB = new CircularReferenceDemo("Ref_B"); objA.setReference(objB); objB.setReference(objA); objA = null; objB = null; } }
Aquí, a pesar de anular las referencias externas, las referencias mutuas entre objA y objB impiden su recolección de basura.
Figura 3 – Referencias circulares
Podemos ver que ya no se puede acceder a ambos objetos. Sin embargo, están referenciados entre sí y, por lo tanto, su recuento de referencias nunca será cero. En consecuencia, nunca se notificará al recolector de GC que los recolecte como basura utilizando el algoritmo de conteo de referencias.
Este algoritmo se implementa prácticamente en C mediante el uso de std::shared_ptr. Diseñado para gestionar el ciclo de vida de objetos asignados dinámicamente, std::shared_ptr automatiza el incremento y disminución de los recuentos de referencias a medida que se crean o destruyen punteros al objeto. Este puntero inteligente forma parte de la biblioteca estándar de C y proporciona capacidades sólidas de administración de memoria que disminuyen significativamente los riesgos asociados con el manejo manual de la memoria. Siempre que se copia un std::shared_ptr, el recuento de referencias internas del objeto administrado aumenta, lo que refleja la nueva referencia. Por el contrario, cuando un std::shared_ptr se destruye, sale del alcance o se reasigna a un objeto diferente, el recuento de referencias disminuye. La memoria asignada se recupera automáticamente y el objeto se destruye cuando su recuento de referencias llega a cero,
evitando eficazmente pérdidas de memoria al garantizar que ningún objeto permanezca asignado sin necesidad.
El algoritmo de análisis de accesibilidad comienza en las raíces del GC y atraviesa el gráfico de objetos. Los objetos a los que no se puede acceder desde estas raíces se consideran irrecuperables y se deben recolectar.
Como se muestra en la imagen a continuación, los objetos en el círculo azul deben mantenerse vivos y los objetos en el círculo gris se pueden reciclar (Figura 4).
Figura 4: pérdida de memoria
Este método resuelve eficazmente el problema de las referencias circulares inherentes al algoritmo de conteo de referencias. Los objetos inalcanzables desde las raíces del GC se clasifican para su recopilación.
Normalmente, los objetos Java considerados como raíces de GC incluyen:
GraalVM ofrece un compilador anticipado (AOT), que traduce aplicaciones Java en archivos binarios ejecutables independientes conocidos como imágenes nativas de GraalVM. Desarrollados por Oracle Labs, estos binarios
encapsule clases de aplicaciones y bibliotecas, y componentes de tiempo de ejecución como el GC, lo que permite operaciones sin un entorno de ejecución de Java (JRE).
El proceso implica análisis estático para determinar los componentes accesibles, inicialización a través de bloques ejecutados y finalización mediante la creación de una instantánea del estado de la aplicación para la posterior traducción del código de máquina.
Substrate VM es una parte integral de la suite GraalVM, orquestada por Oracle Labs. Es una JVM mejorada que no solo admite la compilación anticipada (AOT), sino que también facilita la ejecución de lenguajes más allá de Java, como JavaScript, Python, Ruby e incluso lenguajes nativos como C y C. En esencia, Substrate VM sirve como un marco sofisticado que permite a GraalVM compilar aplicaciones Java en archivos binarios nativos independientes. Estos binarios no dependen de una máquina virtual Java (JVM) convencional para su ejecución, lo que agiliza la implementación y
procesos operativos.
Una de las características principales de Substrate VM es su recolector de basura especializado, que está optimizado para aplicaciones que requieren baja latencia y un uso mínimo de memoria. Este recolector de basura es experto en manejar el diseño de memoria único y el modelo operativo distinto de las imágenes nativas, que difieren considerablemente de las aplicaciones Java tradicionales que se ejecutan en una JVM estándar. La ausencia de un compilador Just-In-Time (JIT) en las imágenes nativas de Substrate VM es una elección estratégica que ayuda a minimizar el tamaño total del ejecutable. Esto se debe a que elimina la necesidad de incluir el compilador JIT y los metadatos asociados, que son sustanciales en tamaño y complejidad.
Además, aunque GraalVM se desarrolla utilizando Java, esto introduce ciertas restricciones, particularmente en términos de acceso a la memoria nativa. Estas restricciones se deben principalmente a preocupaciones de seguridad y a la necesidad de mantener la compatibilidad entre diversas plataformas. Sin embargo, acceder a la memoria nativa es esencial para operaciones óptimas de recolección de basura. Para abordar esto, Substrate VM emplea un conjunto de interfaces especializadas que facilitan interacciones seguras y eficientes con la memoria nativa. Estas interfaces son parte de la arquitectura más amplia de GraalVM y permiten que Substrate VM administre la memoria de manera efectiva de una manera similar a los lenguajes de nivel inferior como C, manteniendo al mismo tiempo la seguridad y capacidad de administración de Java.
En la práctica, estas capacidades hacen de Substrate VM una herramienta extremadamente versátil que mejora la funcionalidad y la eficiencia de las aplicaciones compiladas con GraalVM. Al permitir que los desarrolladores
Aprovechando una gama más amplia de lenguajes de programación y compilándolos en binarios nativos eficientes, Substrate VM traspasa los límites de lo que se puede lograr con los entornos de desarrollo Java tradicionales. Esto lo convierte en un activo invaluable para proyectos de desarrollo de software modernos que exigen alto rendimiento, menor consumo de recursos y soporte de idiomas versátil.
Los elementos destacados de Substrate VM incluyen:
Acceso a memoria simplificado a través de interfaces como Pointer Interface Pointer para operaciones de memoria sin formato y WordBase Interface WordBase para manejar valores del tamaño de una palabra.
División del montón en segmentos preinicializados que contienen objetos inmutables y segmentos de tiempo de ejecución para la asignación dinámica de objetos (Figura 5).
Figura 5: Gestión de memoria en imagen nativa
En tiempo de ejecución, el llamado montón de imágenes en Substrate VM contiene objetos creados durante el proceso de creación de imágenes. Esta sección del montón está preinicializada con datos de la sección de datos del binario ejecutable y es fácilmente accesible al iniciar la aplicación. Los objetos que residen en el montón de imágenes se consideran inmortales; por lo tanto, las referencias dentro de estos objetos son tratadas como punteros raíz por el
recolector de basura. Sin embargo, el GC solo escanea partes del montón de imágenes en busca de punteros raíz, específicamente aquellos que no están marcados como de solo lectura.
Durante el proceso de compilación, los objetos designados como de solo lectura se colocan en una sección específica de solo lectura del montón de imágenes. Dado que estos objetos nunca contendrán referencias a objetos asignados en tiempo de ejecución, no contienen punteros raíz, lo que permite que el GC los omita durante los análisis. De manera similar, los objetos que consisten únicamente en datos primitivos o matrices de tipos primitivos también carecen de punteros raíz. Este atributo agiliza aún más el proceso de recolección de basura, ya que estos objetos se pueden omitir de los escaneos de GC.
Por el contrario, el montón de Java está diseñado para contener objetos ordinarios que se crean dinámicamente durante el tiempo de ejecución. Esta parte del montón está sujeta a una recolección regular de basura para recuperar el espacio ocupado por objetos que ya no están en uso. Se estructura como un montón generacional con mecanismos de envejecimiento, facilitando una gestión eficiente de la memoria en el tiempo.
Esta división entre el montón de imágenes inmortales preinicializadas y el montón de Java administrado dinámicamente permite a Substrate VM optimizar el uso de la memoria y la eficiencia de la recolección de basura, atendiendo a los aspectos estáticos y dinámicos de los requisitos de memoria de la aplicación.
En el modelo de montón de Substrate VM, la memoria se organiza sistemáticamente en estructuras conocidas como fragmentos de montón. Estos fragmentos, que normalmente tienen un tamaño predeterminado de 1024 KB, forman un segmento continuo de memoria virtual que se asigna únicamente al almacenamiento de objetos. La estructura organizativa de estos fragmentos es una lista vinculada donde el fragmento final representa el segmento agregado más recientemente. Un modelo así
facilita la asignación eficiente de memoria y la gestión de objetos.
Estos fragmentos del montón se clasifican además en dos tipos: alineados y no alineados. Los fragmentos de montón alineados son capaces de contener varios objetos de forma continua. Esta alineación permite un mapeo más simple de
objetos a sus respectivos fragmentos del montón principal, lo que hace que la gestión de la memoria sea más intuitiva y eficiente. En escenarios donde la promoción de objetos es necesaria, normalmente durante la recolección de basura y
Optimización de la memoria: un objeto se mueve desde su ubicación original en un fragmento de montón principal a un fragmento de montón de destino ubicado en un "espacio antiguo" designado. Esta migración es parte de la estrategia de gestión del montón generacional que ayuda a optimizar el proceso de recolección de basura al separar los objetos jóvenes de los viejos, reduciendo así la sobrecarga durante los ciclos de GC.
GraalVM Native Image admite varios GC adaptados a diferentes necesidades:
GC en serie: Recopilador predeterminado de tamaño reducido adecuado para aplicaciones de un solo subproceso.
G1 Garbage Collector: Diseñado para aplicaciones multiproceso con tamaños de montón grandes, lo que mejora la flexibilidad en la gestión de generación.
Epsilon GC: Un recopilador minimalista que maneja la asignación pero carece de recuperación, y se utiliza mejor para aplicaciones de corta duración donde la utilización total del montón es predecible.
En conclusión, Substrate VM optimiza eficazmente la gestión de la memoria dentro de GraalVM incorporando técnicas avanzadas como la recolección de basura especializada y la gestión estructurada del montón. Estas características, que incluyen fragmentos de montón y segmentos de memoria separados para montones de imágenes y Java, agilizan la recolección de basura y mejoran el rendimiento de las aplicaciones. Dado que Substrate VM admite una variedad de lenguajes de programación y los compila en binarios nativos eficientes, muestra cómo los marcos JVM modernos pueden extenderse más allá de los límites tradicionales para mejorar la eficiencia y la solidez de la ejecución en diversos entornos de aplicaciones. Este enfoque establece un alto estándar para futuros desarrollos en tecnología de máquinas virtuales e implementación de aplicaciones.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3