El Garbage Collector (GC) se encarga de la gestión de la memoria en Java. Como resultado, los programadores no necesitan ocuparse explícitamente de la asignación y desasignación de memoria.

Java, a través de la JVM, reserva cierta cantidad de memoria antes de que se comience a ejecutar el programa. A veces, la memoria real utilizada es significativamente menor que la cantidad reservada. En tales escenarios, preferimos devolver el exceso de memoria al sistema operativo.

Todo este proceso depende de los algoritmos utilizados para la recolección de basura. En consecuencia, podemos elegir el tipo de GC y JVM según el comportamiento requerido.

En este tutorial, exploraremos la administración de memoria por parte de GC y su interacción con el sistema operativo.

Organización de memoria JVM

Cuando se inicializa JVM, se crean diferentes tipos de áreas de memoria en su interior, como el área de almacenamiento dinámico, el área de pila, el área de método, los registros de PC y la pila de método nativo.

Área de métodos

Es una parte del heap que se comparte entre todos los subprocesos. Se crea cuando se inicia la JVM. Se utiliza para almacenar la estructura de la clase, el nombre de la superclase, el nombre de la interfaz y los constructores. La JVM almacena los siguientes tipos de información en el área de métodos:

  • Un nombre completo de un tipo (por ejemplo, cadena)
  • Los modificadores del tipo
  • Nombre directo de la superclase del tipo
  • Una lista estructurada de los nombres completos de superinterfaces.

Heap

Heap almacena los objetos reales. Se crea cuando se inicia la JVM. El usuario puede controlar el heapsi es necesario. Puede ser de tamaño fijo o dinámico. Cuando usa una nueva palabra clave, la JVM crea una instancia para el objeto en el heap. Mientras que la referencia de ese objeto se almacena en el stack. Solo existe un heap para cada proceso de JVM en ejecución. Cuando el heap se llena, se corre el Garbage Collector.

Stack

El stack se genera cuando se crea un subproceso. Puede ser de tamaño fijo o dinámico. La memoria del stack se asigna por subproceso. Se utiliza para almacenar datos y resultados parciales. Contiene referencias a objetos del heap. También contiene el valor en sí mismo en lugar de una referencia a un objeto del heap. Las variables que se almacenan en el stack tienen cierta visibilidad, llamada alcance.

Stack Frame

El stack frame es una estructura de datos que contiene los datos del subproceso. Los datos del subproceso representan el estado del subproceso en el método actual.

Cada frame contiene:

  • LVA Local Variable Array
  • OS Operand Stack
  • FD Frame Data

Se utiliza para almacenar resultados parciales y datos. También realiza enlaces dinámicos, devolución de valores por métodos y despacho de excepciones.

Entonces…

GC se ocupa del almacenamiento en el heap. Por lo tanto, nos centraremos en la interacción de la memoria relacionada con el montón en este artículo.

Podemos especificar los tamaños de almacenamiento dinámico iniciales y máximos mediante los indicadores -Xms y -Xmx, respectivamente. Si -Xms es menor que -Xmx, implica que la JVM no ha asignado toda la memoria reservada al heap al principio. En resumen, el tamaño del montón comienza desde -Xms y puede expandirse hasta -Xmx. Esto permite que un desarrollador configure el tamaño de la memoria de almacenamiento dinámico requerida.

Ahora, cuando se ejecuta la aplicación, se asigna memoria a diferentes objetos dentro del heap. En el momento de la recolección de elementos no utilizados, GC desasigna los objetos sin referencia y libera la memoria. Esta memoria desasignada actualmente forma parte del heap, ya que es un procedimiento intensivo de la CPU para interactuar con el sistema operativo después de cada desasignación.

Los objetos residen de manera dispersa dentro del heap. GC necesita compactar la memoria y crear un bloque libre para volver al sistema operativo. Implica la ejecución de un proceso adicional al devolver la memoria. Además, las aplicaciones Java pueden necesitar memoria adicional en una etapa posterior. Para esto, necesitamos comunicarnos nuevamente con el sistema operativo para solicitar más memoria. Además, no podemos garantizar la disponibilidad de memoria en el sistema operativo en el momento solicitado. Por lo tanto, es un enfoque más seguro utilizar el almacenamiento dinámico interno en lugar de llamar con frecuencia al sistema operativo para obtener memoria.

Sin embargo, si nuestras aplicaciones no requieren toda la memoria del heap, simplemente estamos bloqueando los recursos disponibles, que el sistema operativo podría haber utilizado para otras aplicaciones. Teniendo esto en cuenta, la JVM ha introducido técnicas eficientes y automatizadas para la liberación de memoria.

Garbage Collectors

Avanzando en diferentes versiones de lanzamiento, Java ha introducido diferentes tipos de GC. La interacción de la memoria entre el heap y el sistema operativo depende de las implementaciones de JVM y GC. Algunas implementaciones de GC admiten activamente la reducción del heap. La reducción del heap es el proceso de liberar el exceso de memoria del montón al sistema operativo para un uso óptimo de los recursos.

Por ejemplo, Parallel GC no devuelve fácilmente la memoria no utilizada al sistema operativo. Por otro lado, algunos GC analizan el consumo de memoria y determinan en consecuencia liberar algo de memoria libre del montón. Los GC G1, Serial, Shenandoah y Z admiten la reducción del almacenamiento dinámico.

Exploremos estos procesos ahora.

Garbage First (G1) GC

G1 ha sido el GC predeterminado desde Java 9. Admite procesos de compactación sin largas pausas. Utilizando algoritmos internos de optimización adaptativa, analiza la RAM requerida según el uso de la aplicación y libera la memoria si es necesario.

Las implementaciones iniciales admiten la reducción del almacenamiento dinámico después de completar el GC o durante eventos de ciclo simultáneos. Sin embargo, para una situación ideal, queremos devolver rápidamente la memoria no utilizada al sistema operativo, especialmente durante los períodos en que nuestra aplicación está inactiva. Queremos que el GC se adapte dinámicamente al uso de memoria de nuestras aplicaciones en tiempo de ejecución.

Java ha incluido tales capacidades en diferentes GC. Para G1, JEP 346 introduce estos cambios. A partir de Java 12 y versiones posteriores, la reducción de almacenamiento dinámico también es posible en la fase de comentarios concurrentes. G1 intenta analizar el uso del almacenamiento dinámico cuando la aplicación está inactiva y activa la recolección periódica de elementos no utilizados según sea necesario. G1 puede iniciar un ciclo simultáneo o un GC completo según la opción -XX:+G1PeriodicGCInvokesConcurrent. Después de que se ejecuta el ciclo, G1 necesita cambiar el tamaño del montón y devolver la memoria liberada al sistema operativo.

Serial GC

Serial GC también admite el comportamiento de reducción del almacenamiento dinámico. En comparación con G1, requiere cuatro ciclos de GC completos adicionales para liberar la memoria liberada.

ZGC

ZGC se introdujo con Java 11. También se mejoró con la funcionalidad para devolver la memoria no utilizada al sistema operativo en JEP 351.

Shenandoah GC

Shenandoah es un GC concurrente. Realiza la recolección de basura de forma asíncrona. Eliminar la necesidad de un GC completo es de gran ayuda en la optimización del rendimiento de la aplicación.

Banderas de la JVM

Hemos visto anteriormente que podemos especificar tamaños de almacenamiento dinámico utilizando las opciones de línea de comandos de JVM. De manera similar, podemos usar diferentes indicadores para configurar el comportamiento de reducción de almacenamiento dinámico predeterminado de un GC:

  • -XX:GCTimeRatio: para especificar la división de tiempo deseada entre la ejecución de la aplicación y la ejecución del GC. Podemos usarlo para hacer que el GC funcione más tiempo
  • -XX:MinHeapFreeRatio: para especificar la proporción mínima esperada de espacio libre en el montón después de la recolección de elementos no utilizados
  • -XX: MaxHeapFreeRatio: para especificar la proporción máxima esperada de espacio libre en el montón después de la recolección de elementos no utilizados

Si el espacio libre disponible en el heap es mayor que la proporción especificada mediante la opción -XX:MaxHeapFreeRatio, entonces GC puede devolver la memoria no utilizada al sistema operativo. Podemos configurar el valor de los indicadores anteriores para restringir la cantidad de memoria no utilizada en el montón. Tenemos opciones similares disponibles para procesos de recolección de basura simultáneos:

  • -XX:InitiatingHeapOccupancyPercent: para especificar el porcentaje de ocupación de almacenamiento dinámico necesario para iniciar una recolección de elementos no utilizados concurrente.
  • -XX:-ShrinkHeapInSteps: para reducir el tamaño del almacenamiento dinámico al valor -XX:MaxHeapFreeRatio inmediatamente. La implementación predeterminada requiere varios ciclos de recolección de elementos no utilizados para este proceso.

Categorized in: