Siete tipos de recolectores de basura en JAVA

Este documento explicará qué es el recolector de basura en java y los principales tipos de recolectores de basura con los comportamientos de cada recolector de basura. También tenga en cuenta que en este artículo no estoy explicando cómo ocurre la asignación de la pila y el inicio de los recolectores de basura mayores y menores. Se explicará en el próximo artículo.

Java es un lenguaje de programación orientado a objetos que incluye Automatic Garbage Collection. Java asigna y desasigna automáticamente la memoria para que los programas no tengan que cargar con esa tarea. Ahora (Java-12), Java tiene siete tipos de recolectores de basura,

  1. Colector de Basura Serial
  2. Colector de Basura Paralelo
  3. CMS Colector de Basura
  4. G1 Colector de Basura
  5. Epsilon Colector de Basura
  6. Z garbage collector
  7. Shenandoah Garbage Collector

5º y 6º recolectores de basura se han introducido en java 11 y el 7º se ha introducido en java 12. En la mayoría de las aplicaciones en la producción ha utilizado los primeros cuatro tipos de colectores de basura. Desde los últimos tres recolectores de basura recientemente entran en escena.

¿Tenemos que preocuparnos por los GCs?

Sí, efectivamente. Tenemos que preocuparnos por el GC y sus comportamientos. Porque puede proporcionar una diferencia de rendimiento significativa. Cada GC tiene sus propias ventajas y desventajas. Como desarrolladores, tenemos que tener una idea clara sobre los comportamientos de todos los recolectores de basura y tenemos que seleccionar un recolector de basura basado en nuestro escenario de negocio. Podemos elegir un recolector de basura, pasando la elección como un argumento de la JVM.

Serial Garbage Collector

Esta es la implementación de GC más simple. Está básicamente diseñada para un entorno de un solo hilo. Esta implementación de GC congela todos los hilos de la aplicación cuando se ejecuta. Utiliza un solo hilo para la recolección de basura. Por lo tanto, no es una buena idea utilizarlo en aplicaciones multihilo como los entornos de servidor.

Para activar el recolector de basura en serie, podemos utilizar el siguiente argumento:

java -XX:+UseSerialGC -jar Application.java

Colector de basura en paralelo

El recolector de basura en paralelo también se denomina recolector de rendimiento. A diferencia del recolector de basura en serie, este utiliza múltiples hilos para la recolección de basura. Al igual que el recolector de basura en serie, este también congela todos los hilos de la aplicación mientras realiza la recolección de basura. El recolector de basura es más adecuado para aquellas aplicaciones que pueden soportar las pausas de la aplicación.

Para habilitar el Garbage Collector paralelo, podemos utilizar el siguiente argumento:

java -XX:+UseParallelGC -jar Application.java

Si utilizamos este GC, podemos especificar el número máximo de hilos de recolección de basura y el tiempo de pausa, el rendimiento y la huella (tamaño de la pila)

El número de hilos del recolector de basura se puede controlar con la opción de línea de comandos

-XX:ParallelGCThreads=<N>

El objetivo de tiempo de pausa máximo (intervalo entre dos GC)se especifica con la opción de línea de comandos

-XX:MaxGCPauseMillis=<N>

El objetivo de rendimiento máximo (medido con respecto al tiempo que se pasa haciendo la recolección de basura frente al tiempo que se pasa fuera de la recolección de basura) se especifica con la opción de línea de comandos

-XX:GCTimeRatio=<N>

La huella máxima del heap (la cantidad de memoria del heap que requiere un programa mientras se ejecuta) se especifica mediante la opción -Xmx<N>.

CMS Garbage Collector

El recolector de basura Concurrent Mark Sweep (CMS) utiliza múltiples hilos de recolección de basura. Escanea la memoria del montón para marcar instancias para su desalojo y luego barre las instancias marcadas. Está diseñado para aplicaciones que prefieren pausas de recolección de basura más cortas y que pueden permitirse compartir los recursos del procesador con el recolector de basura mientras la aplicación se está ejecutando.

El recolector de basura CMS mantiene todos los hilos de la aplicación sólo en los dos escenarios siguientes

  1. Durante el marcado de los objetos referenciados en el espacio de generación antiguo.
  2. Cualquier cambio en la memoria del heap en paralelo con la realización de la recolección de basura

En comparación con el recolector de basura paralelo, el recolector CMS utiliza más CPU para asegurar un mejor rendimiento de la aplicación. Si podemos asignar más CPU para un mejor rendimiento, entonces el recolector de basura CMS es la opción preferida sobre el recolector paralelo.

Para habilitar el recolector de basura CMS, podemos utilizar el siguiente argumento:

java -XX:+USeParNewGC -jar Application.java

G1 Garbage Collector

G1 (Garbage First) Garbage Collector está diseñado para aplicaciones que se ejecutan en máquinas multiprocesadoras con gran espacio de memoria. Está disponible desde la actualización 4 de JDK7 y en versiones posteriores.

Separa la memoria del heap en regiones y hace la recolección dentro de ellas en paralelo. G1 también compacta el espacio libre del heap sobre la marcha justo después de reclamar la memoria. Pero el recolector de basura CMS compacta la memoria en situaciones de parada del mundo (STW). El recolector G1 reemplazará al recolector CMS ya que es más eficiente en cuanto a rendimiento.

El recolector G1 contiene dos fases;

  1. Marcado
  2. Barrido

A diferencia de otros recolectores, el recolector G1 particiona la pila en un conjunto de regiones de pila de igual tamaño, cada una un rango contiguo de memoria virtual. Al realizar las recolecciones de basura, G1 muestra una fase de marcado global concurrente para determinar la vitalidad de los objetos en todo el montón.

Después de completar la fase de marcado, G1 sabe qué regiones están mayormente vacías. Recoge en estas áreas primero, lo que suele producir una cantidad significativa de espacio libre. Es por ello que este método de recolección de basura se llama Garbage-First.

Para habilitar el recolector de basura G1, podemos utilizar el siguiente argumento:

java -XX:+UseG1GC -jar Application.java

Epsilon Garbage Collector

Epsilon es un recolector de basura no operativo o pasivo. Asigna la memoria para la aplicación, pero no recoge los objetos no utilizados. Cuando la aplicación agota el heap de Java, la JVM se apaga. Esto significa que el recolector de basura de Epsilon permite que las aplicaciones se queden sin memoria y se bloqueen.

El propósito de este recolector de basura es medir y gestionar el rendimiento de la aplicación. Los recolectores de basura activos son programas complejos que se ejecutan dentro de la JVM junto a su aplicación. Epsilon elimina el impacto que la GC tiene en el rendimiento. No hay ciclos de GC ni barreras de lectura o escritura. Cuando se utiliza el GC de Epsilon, el código se ejecuta de forma aislada. Epsilon ayuda a visualizar cómo afecta la recolección de basura al rendimiento de la aplicación y cuál es el umbral de memoria, ya que mostrará cuándo se agota. Como ejemplo, si pensamos que sólo necesitamos un gigabyte de memoria para nuestra aplicación, podemos ejecutarla con -Xmx1g y ver el comportamiento. Si esa asignación de memoria no es suficiente, vuelve a ejecutarlo con un volcado de heap. Ten en cuenta que tenemos que activar esta opción para obtener un volcado de heap. Podemos utilizar este argumento para obtener un volcado de heap cuando la aplicación se cuelga por falta de memoria.

XX:HeapDumpOnOutOfMemoryError

Si necesitamos exprimir cada bit de rendimiento de nuestra aplicación, Epsilon podría ser tu mejor opción para un GC. Pero tenemos que tener un conocimiento completo de cómo nuestro código utiliza la memoria. Si casi no crea basura o sabes exactamente cuánta memoria utiliza para el período en que se ejecuta, Epsilon es una opción viable.

Para habilitar el recolector de basura Epsilon, podemos utilizar el siguiente argumento:

java -XX:+UseEpsilonGC -jar Application.java

Z garbage collector

ZGC realiza todo el trabajo costoso de forma concurrente, sin detener la ejecución de los hilos de la aplicación durante más de 10ms, lo que lo hace adecuado para aplicaciones que requieren baja latencia y/o utilizan un heap muy grande. Según la documentación de Oracle, puede manejar heaps de varios terabytes. Oracle introdujo el ZGC en Java 11. El recolector de basura Z realiza sus ciclos en sus hilos. Pone en pausa la aplicación durante una media de 1 ms. Los recolectores G1 y Parallel tienen una media de 200 ms.

En Java 12, Oracle añadió correcciones de rendimiento y descarga de clases aunque Z sigue en estado experimental. Sólo está disponible en Linux de 64 bits. Pero, ZGC aprovecha los punteros de 64 bits con una técnica llamada coloreado de punteros. Los punteros coloreados almacenan información extra sobre los objetos de la pila. Esta es una de las razones por las que está limitado a la JVM de 64 bits. En este artículo se ha explicado este escenario en profundidad (https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/).

ZGC realiza su marcado en tres fases.

1. Fase corta de parada del mundo – Examina las raíces del GC, variables locales que apuntan al resto del montón. El número total de estas raíces suele ser mínimo y no escala con el tamaño de la carga, por lo que las pausas de ZGC son muy cortas y no aumentan a medida que su montón crece.

2. Fase concurrente – Recorre el gráfico de objetos y examina los punteros de color, marcando los objetos accesibles. La barrera de carga evita la contención entre la fase GC y la actividad de cualquier aplicación.

3. Fase de reubicación – Mueve los objetos vivos para liberar grandes secciones del montón y hacer las asignaciones más rápidas. Cuando comienza la fase de reubicación, ZGC divide el montón en páginas y trabaja en una página a la vez. Una vez que ZGC termina de mover cualquier raíz, el resto de la reubicación ocurre en una fase concurrente.

ZGC intentará establecer el número de hilos por sí mismo, y normalmente acierta. Pero si ZGC tiene demasiados hilos, matará de hambre a tu aplicación. Si no tiene suficientes, creará basura más rápido de lo que el GC puede recogerla. Las fases de ZGC ilustran cómo gestiona grandes heaps sin afectar al rendimiento a medida que crece la memoria de la aplicación.

Para habilitar Z Garbage Collector, podemos utilizar el siguiente argumento:

java -XX:+UseZGC -jar Application.java

Shenandoah

Shenandoah es un recolector de basura de tiempo de pausa ultra bajo que reduce los tiempos de pausa de GC realizando más trabajo de recolección de basura concurrentemente con el programa Java en ejecución. Tanto CMS como G1 realizan un marcado concurrente de objetos vivos. Shenandoah añade compactación concurrente.

Shenandoah utiliza regiones de memoria para gestionar qué objetos ya no están en uso y cuáles están vivos y listos para la compresión. Shenandoah también añade un puntero de reenvío a cada objeto del montón y lo utiliza para controlar el acceso al objeto. El diseño de Shenandoah intercambia ciclos de CPU concurrentes y espacio por mejoras en el tiempo de pausa. El puntero de reenvío facilita el movimiento de objetos, pero los movimientos agresivos significan que Shenandoah utiliza más memoria y requiere más trabajo paralelo que otros GC. Pero hace el trabajo extra con pausas muy breves de parar el mundo.

Shenandoah procesa el montón en muchas fases pequeñas, la mayoría de las cuales son concurrentes con la aplicación. Este diseño hace posible que el GC gestione un montón grande de forma eficiente.

  1. Primera pausa stop-the-world del ciclo. Prepara el heap para el marcado concurrente y escanea el conjunto raíz. Al igual queZGC, la duración de esta pausa corresponde al tamaño del conjunto raíz, no al del montón.
  2. A continuación, una fase concurrente recorre el montón e identifica los objetos alcanzables y no alcanzables.
  3. La tercera finaliza el proceso de marcado drenando las actualizaciones pendientes del montón y volviendo a escanear el conjunto raíz. Esta fase desencadena la segunda pausa de parada del mundo en el ciclo. El número de actualizaciones pendientes y el tamaño del conjunto raíz determinan la duración de la pausa.
  4. A continuación, otra fase concurrente copia los objetos fuera de las regiones identificadas en la fase final de marcado. Este proceso diferencia a Shenandoah de otros GCs ya que compacta agresivamente el montón en paralelo con los hilos de la aplicación.
  5. La siguiente fase desencadena la tercera (y más corta) pausa del ciclo. Asegura que todos los hilos de la GC han terminado la evacuación.
  6. Cuando termina, una fase concurrente recorre el montón y actualiza las referencias a los objetos movidos anteriormente en el ciclo.
  7. La última pausa del ciclo termina de actualizar las referencias actualizando el conjunto raíz. Al mismo tiempo, recicla las regiones evacuadas.
  8. Por último, la última fase recupera las regiones evacuadas, que ahora no tienen referencias en ellas.

Podemos configurar Shenandoah con una de las tres heurísticas. Éstas gobiernan cuándo el CG comienza sus ciclos y cómo selecciona las regiones para la evacuación.

1. Adaptativa: Observa los ciclos de la GC e inicia el siguiente ciclo para que se complete antes de que la aplicación agote el montón. Esta heurística es el modo por defecto.

2. Static: Inicia un ciclo de GC basado en la ocupación del heap y la presión de asignación.

3. Compact: Ejecuta ciclos de GC de forma continua. Shenandoah inicia un nuevo ciclo tan pronto como termina el anterior o en función de la cantidad de heap asignada desde el último ciclo. Esta heurística incurre en una sobrecarga de rendimiento, pero proporciona la mejor recuperación de espacio.

Shenandoah necesita recoger la pila más rápido de lo que la aplicación a la que sirve la asigna. Si la presión de asignación es demasiado alta y no hay suficiente espacio para nuevas asignaciones, habrá un fallo. Shenandoah tiene mecanismos configurables para esta situación.

  • Pacing: Si Shenandoah empieza a quedarse atrás en el ritmo de asignación, detendrá los hilos de asignación para ponerse al día. Las paradas suelen ser suficientes para los picos de asignación leves. Shenandoah introduce retrasos de 10ms o menos. Si el ritmo falla, Shenandoah pasará al siguiente paso: GC degenerada.
  • GC degenerada: Si se produce un fallo de asignación, Shenandoah inicia una fase de parada del mundo. Utiliza la fase para completar el ciclo de GC actual. Dado que una parada del mundo no compite con la aplicación por los recursos, el ciclo debería terminar rápidamente y eliminar el déficit de asignación. A menudo, un ciclo degenerado ocurre después de que la mayor parte del trabajo del ciclo ya se ha completado, por lo que la parada del mundo es breve. Sin embargo, el registro de GC lo reportará como una pausa completa.
  • GC completa: Si tanto el ritmo como la GC degenerada fallan, Shenandoah vuelve a un ciclo de GC completa. Esta GC final garantiza que la aplicación no fallará con un error de falta de memoria a menos que no quede ningún heap.

Shenandoah ofrece las mismas ventajas que ZGC con heaps grandes pero más opciones de ajuste. Dependiendo de la naturaleza de su aplicación, las diferentes heurísticas pueden ser un buen ajuste. Sus tiempos de pausa pueden no ser tan breves como los de ZGC, pero son más predecibles.

Para habilitar el Shenandoah Garbage Collector, podemos utilizar el siguiente argumento:

java -XX:+UseShenanodoahC -jar Application.java

Conclusión

El objetivo de este artículo es resumir todos los recolectores de basura. Por lo tanto, algunas partes del contenido se han extraído de las referencias dadas. Tenemos que tener una idea clara sobre los recolectores de basura para seleccionar un recolector de basura óptimo para nuestros casos de uso de la aplicación. El recolector de basura óptimo mejorará significativamente el rendimiento de nuestra aplicación.

Referencia

  1. https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
  2. https://www.baeldung.com/jvm-garbage-collectors
  3. https://dzone.com/articles/java-garbage-collection-3
  4. https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/
  5. https://openjdk.java.net/projects/shenandoah/
  6. https://docs.oracle.com/en/java/javase/12/gctuning/z-garbage-collector1.html#GUID-A5A42691-095E-47BA-B6DC-FB4E5FAA43D0
  7. http://clojure-goes-fast.com/blog/shenandoah-in-production/

Deja una respuesta

Tu dirección de correo electrónico no será publicada.