Garbage Collector

4
12602

En este tutorial vamos a conocer los aspectos básicos la gestión de memoria de la plataforma Java y los distintos Garbage Collector disponibles, así como su funcionamiento.

El Heap es un espacio de memoria dinámico, es decir, aumenta de tamaño según sea necesario. Cuando el Heap se queda sin espacio, existen dos posibilidades, o bien aumentar el tamaño del mismo, o bien liberar espacio, y es en esta última solución donde aparecen los recolectores de basura (ó GCs, Garbage Collectors).

0. Índice de contenidos.

  1. Introducción.
  2. Clasificación.
  3. "Weak Generational Hypothesis" y la estructura del heap.
  4. GCs.
  5. Herramientas y configuración.

1. Introducción.

Como ya hemos visto en anteriores tutoriales, la memoria de la JVM se divide en: zonas o estructuras existentes para cada hilo de ejecución (Stack) y una zona común para todos los hilos. A su vez, la zona común para todos los hilos, puede separarse en diversas secciones entre las cuales se encuentra el Heap, la memoria encargada de almacenar la información de los objetos, y sus valores, que se están utilizando en nuestras aplicaciones Java.

El Heap es un espacio de memoria dinámico, es decir, aumenta de tamaño según sea necesario. Cuando el Heap se queda sin espacio, existen dos posibilidades, o bien aumentar el tamaño del mismo, o bien liberar espacio, y es en esta última solución donde aparecen los recolectores de basura (ó GCs, Garbage Collectors).

Un GC es una aplicación que se ejecuta dentro de la JVM y se encarga de gestionar el Heap, liberando automáticamente la memoria ocupada por aquellos objetos que no van a ser utilizados en un programa.

Entre las ventajas del uso del GCs podemos encontrar:

  • Se quita la responsabilidad de liberar y destruir los objetos a los programadores.
  • Garantiza la interoperabilidad entre distintas APIs, librerías o frameworks.
  • Facilita el uso de grandes cantidades de memoria.

Por contra, entre las desventajas podemos encontrar:

  • Consumo adicional de recursos.
  • Impacto en el rendimiento de aplicaciones.
  • Posibles paradas en la ejecución de las aplicaciones.
  • Incompatibilidad con la gestión manual de la memoria.

2. Clasificación.

Podemos clasificar los GCs en base a sus características:

  • Concurrente vs Stop-The-World:

    –> Concurrentes son aquellos que pueden realizar su trabajo a la vez que se ejecuta una aplicación.

    –> STW son aquellos que paran la ejecución de la aplicación para realizar sus tareas de limpieza.

  • Paralelo vs Serie:

    –> Paralelos son aquellos que pueden aprovechar la existencia de múltiples CPUs para realizar su trabajo.

    –> Serie son aquellos que se ejecutan en un único hilo.

  • Incremental vs Monolítico:

    –> Incrementales son aquellos dividen su trabajo en diferentes fases u operaciones, siendo estas fases separadas en el tiempo.

    –> Monolíticos son aquellos que realizan su trabajo en una única tarea.

  • Precisos vs Conservadores:

    –> Precisos son aquellos que pueden identificar y procesar todas las referencias de los objetos en el momento de la recolección.

    –> Conservadores son aquellos que desconocen algunas de las referencias de los objetos en el momento de la recolección, o que no están seguros de si un campo es una referencia o no.

  • Compactadores vs No Compactadores:

    –> Los compactadores son aquellos que, tras realizar las tareas de limpieza mueven los objetos, actualizando sus referencias, minimizando de esta manera la fragmentación de la memoria. Esta operación implica ser STW.

    –> Los no compactadores son aquellos que se limitan a eliminar la basura de la memoria.

3. "Weak Generational Hypothesis" y la estructura del heap

Los GC suelen crearse basándose en dos suposiciones o precondiciones denominadas "Weak Generational Hypothesis":

  • La mayoría de los objetos dejan de ser útiles al poco tiempo de ser instanciados.
  • Hay pocas probabilidades de que los objetos "viejos" referencien a los objetos "jóvenes".

Como hemos comentado anteriormente el Heap es el espacio de memoria encargado de mantener los objetos que están siendo utilizados en nuestro programa. Estos se encuentran organizados dentro del heap, en base a las suposiciones listadas al comienzo de este punto, en distintas secciones:

  • Los objetos jóvenes se organizan en 3 espacios distintos:

    –> Eden.

    –> espacios de supervivientes.

  • Los objetos viejos se mantienen en un único espacio de memoria.

La manera en la que los objetos promocionan entre los distintos espacios dentro del heap se describe a continuación:

  1. – La mayoría de los objetos, al ser instanciados, se almacenan en Eden.
  2. – Con cada pasada del GC por Eden los objetos supervivientes son promocionados al grupo de supervivientes.
  3. – Cuando uno de los espacios de supervivientes se completa, los objetos supervivientes de ese espacio se desplazan al siguiente, liberando los dos espacios restantes (Eden y el otro espacio de supervivientes).
    • Nota: Los roles dentro de los espacios de supervivientes (S0 y S1) van rotando, es decir, en algunas ocasiones el espacio S0 será el primero en llenarse y el que popule la información a S1 y en otras ocasiones sera a la inversa, el espacio S1 será el primero en llenarse y traspasará la información a S0.
  4. – Cuando el segundo espacio de supervivientes se completa, los objetos de este último espacio son promocionados al espacio de objetos antiguos.

4. GCs.

Recolector Serie.

Recolector que trabaja sobre una única CPU tanto para los objetos de generación jóven como de generación vieja. Este recolector es el recomendado para sistemas embebidos que no tienen mucha capacidad de procesamiento.

Recolector Paralelo.

Recolecta los objetos de generación jóven en paralelo, mientras que los de generación vieja los recolecta en serie. Este recolector es útil cuando se tienen multiples CPUs.

Recolector Paralelo con compactación.

Tanto los objetos de generación joven como de generación vieja se recolectan en paralelo y además se compactan.

Recolector CMS (Concurrent Mark Sweep).

Utiliza un algoritmo Mark / Sweep para recolectar los objetos que consiste en:

  1. – Revisión del heap identificando los objetos referenciados y marcándolos como vivos y los no referenciados y marcandolos como muertos.
  2. – De nuevo se vuelve a revisar el heap buscando los objetos marcados como muertos y liberando los espacios de memoria ocupados por estos.

Este recolector no mantiene la compactación activa por defecto y utiliza más memoria y CPU que los anteriores recolectores en favor de mantener unos períodos STW más cortos.

Recolector G1.

Este tipo de recolector toma un enfoque distinto, no divide el heap en posiciones correlativas de Eden, supervivientes y objetos antiguos, sino que mantiene celdas de igual tamaño que van adquiriendo roles, sin un tamaño concreto para cada espacio.

Este recolector actúa de manera similar al CMS, pero después del marcado ya sabe que regiones de memoria están vacías, y por ello, recolecta en estas zonas en primera instancia. Otra diferencia con respecto al recolector CMS es que si compacta la memoria.

5. Herramientas y configuración.

Podemos controlar las características y la configuración del heap de la JVM a través de distintas políticas.

Para saber cuales son las políticas que están aplicando sobre nuestra instalación por defecto, bastará con acceder a un terminal e introducir la siguiente línea: java -XX:+PrintCommandLineFlags -version.

A continuación se detallan algunas de las políticas que pueden utilizarse para configurar la JVM.

Configuración del HEAP

Para modificar tamaños de heap:

  • -Xms: Establece el tamaño inicial del heap.
  • -Xmx: Establece el tamaño máximo del heap.
  • -Xmn: Establece el tamaño del espacio del heap para objetos de generación jóven.
  • -XX:SurvivorRatio=: Establece el tamaño del espacio de supervivientes. Establece el ratio de tamaño entre cada espacio de supervivientes y Eden.

Para monitorizar el heap:

  • -XX:HeapDumpPath=./java_pid.hprof: Establece la ruta donde se generará el dump del heap.
  • -XX:+HeapDumpOnOutOfMemoryError: Establece que se haga un dump del heap cuando se produzca un OutOfMemoryException.
  • -XX:OnOutOfMemoryError=";": Lanza comandos definidos por el usuario cuando se produzca un OutOfMemoryException.

Recolectores

Para cambiar el recolector de basura:

  • -XX:+UseSerialGC: Recolector Serie.
  • -XX:+UseParallelGC: Recolector Paralelo

    –> -XX:ParallelGCThreads=: Establecer el número de hilos para ejecutar el GC.

  • -XX:+UseParallelOldGC: Recolector Paralelo con compactación.
  • -XX:+UseConcMarkSweepGC: Recolector CMS.

    –> -XX:ParallelCMSThreads=: Establecer el número de hilos para ejecutar el GC.

  • -XX:+UseG1GC: Recolector G1.

Para monitorizar el GC:

  • -XX:+PrintGC: Imprime un mensaje cada vez que se ejecuta el recolector.
  • -XX:+PrintGCDetails – Imprime un mensaje más detallado que el anterior cada vez que se ejecuta el recolector.
  • -XX:+PrintGCTimeStamps – Imprime un time stamp relativo a la hora de inicio de la JVM cuando se ejecuta el recolector.
  • -XX:+PrintGCDateStamps – Imprime fecha y timestamp cuando se ejecuta el recolector.
  • -Xloggc: – Almacena los logs del recolector en un fichero en lugar de por terminal.

4 Comentarios

  1. Gracias por el post y la info. Estoy teniendo problemas en microservicios en docker que no me liberan correctamente el heap, y con el paso del tiempo acaba dando un OutOfMemory. Voy a probar con los comandos que habeis expuesto a ver si hay suerte.

    Saludos!

Dejar respuesta

Please enter your comment!
Please enter your name here