Recolección de Basura en Java

La recolección de basura en Java es uno de los temas avanzados. El conocimiento de GC en Java nos ayuda a ajustar finamente el rendimiento en tiempo de ejecución de nuestra aplicación.

Recolección de Basura en Java

  • En Java, los programadores no necesitan encargarse de destruir los objetos que ya no están en uso. El Recolector de Basura se encarga de ello.
  • El Recolector de Basura es un hilo Daemon que se ejecuta en segundo plano. Básicamente, libera la memoria del montón (heap) destruyendo los objetos inalcanzables.
  • Los objetos inalcanzables son aquellos que ya no son referenciados por ninguna parte del programa.
  • Podemos elegir el recolector de basura para nuestro programa Java a través de las opciones de JVM, lo veremos en la siguiente sección de este tutorial.

¿Cómo funciona la Recolección Automática de Basura?

La recolección automática de basura es un proceso que consiste en examinar la memoria de montón, identificar (también conocido como “marcar”) los objetos inaccesibles y destruirlos mediante compactación. Un problema con este enfoque es que, a medida que aumenta el número de objetos, el tiempo de recolección de basura sigue aumentando, ya que necesita recorrer toda la lista de objetos en busca del objeto inaccesible. Sin embargo, el análisis empírico de las aplicaciones muestra que la mayoría de los objetos tienen una vida corta. Este comportamiento se utilizó para mejorar el rendimiento de la JVM, y la metodología adoptada se llama comúnmente Recolección de Basura Generacional. En este método, el espacio de montón se divide en generaciones como la Generación Joven, la Generación Vieja o Envejecida y la Generación Permanente. El espacio de montón de la generación joven es el nuevo donde se crean todos los nuevos objetos. Una vez que se llena, se lleva a cabo una recolección de basura menor (también conocida como GC Menor). Lo que significa que todos los objetos muertos de esta generación son destruidos. Este proceso es rápido porque, como podemos ver en el gráfico, la mayoría de ellos estarían muertos. Los objetos supervivientes en la generación joven envejecen y eventualmente se trasladan a las generaciones más antiguas. La Generación Vieja se utiliza para almacenar objetos que sobreviven durante mucho tiempo. Por lo general, se establece un umbral para el objeto de generación joven y cuando se alcanza esa edad, el objeto se mueve a la generación vieja. Eventualmente, la generación vieja necesita ser recolectada. Este evento se llama una GC Mayor (recolección de basura mayor). A menudo es mucho más lento porque implica todos los objetos vivos. También existe la GC Completa, que significa limpiar todo el montón, tanto el espacio de generación joven como el de generación vieja. Por último, hasta Java 7, existía una Generación Permanente (o Perm Gen), que contenía metadatos requeridos por la JVM para describir las clases y métodos utilizados en la aplicación. Se eliminó en Java 8.

Recolectores de basura de Java

El JVM proporciona en realidad cuatro recolectores de basura diferentes, todos ellos generacionales. Cada uno tiene sus propias ventajas y desventajas. La elección de qué recolector de basura usar depende de nosotros y puede haber diferencias dramáticas en el rendimiento y las pausas de la aplicación. Todos estos dividen el montón gestionado en diferentes segmentos, utilizando las antiguas suposiciones de que la mayoría de los objetos en el montón tienen una vida corta y deben reciclarse rápidamente. Por lo tanto, los cuatro tipos de recolectores de basura son:

GC Serial

Este es el recolector de basura más simple, diseñado para sistemas de un solo hilo y tamaño de montón pequeño. Congela todas las aplicaciones mientras trabaja. Se puede activar usando la opción -XX:+UseSerialGC del JVM.

GC Paralelo/De rendimiento

Este es el recolector predeterminado de la JVM en JDK 8. Como su nombre sugiere, utiliza varios hilos para escanear el espacio de la memoria y realizar la compactación. Una desventaja de este recolector es que pausa los hilos de la aplicación mientras realiza una recolección menor o completa. Es más adecuado para aplicaciones que pueden manejar estas pausas y tratan de optimizar la sobrecarga de CPU causada por el recolector.

El recolector CMS

El algoritmo del recolector CMS (“concurrent-mark-sweep”) utiliza varios hilos (“concurrent”) para escanear el montón de memoria (“mark”) en busca de objetos no utilizados que se pueden reciclar (“sweep”). Este recolector entra en modo Stop-The-World (STW) en dos casos: – Durante la inicialización del marcado inicial de las raíces, es decir, los objetos en la generación antigua que son alcanzables desde los puntos de entrada de los hilos o variables estáticas. – Cuando la aplicación ha cambiado el estado del montón de memoria mientras el algoritmo se estaba ejecutando de manera concurrente y lo obliga a retroceder y realizar algunos ajustes finales para asegurarse de que los objetos estén marcados correctamente. Este recolector puede enfrentar fallas en la promoción. Si algunos objetos de la generación joven deben moverse a la generación antigua y el recolector no tuvo suficiente tiempo para liberar espacio en la generación antigua, se producirá una falla en la promoción. Para prevenir esto, podemos asignar más espacio en el montón de memoria a la generación antigua o proporcionar más hilos en segundo plano al recolector.

Recolector G1

Por último, pero no menos importante, tenemos el recolector Garbage-First, diseñado para tamaños de montón mayores a 4GB. Divide el tamaño del montón en regiones que van desde 1MB hasta 32MB, según el tamaño del montón. Hay una fase de marcado global concurrente para determinar la vivacidad de los objetos en todo el montón. Después de que la fase de marcado esté completa, G1 sabe qué regiones están mayormente vacías. Recoge los objetos inalcanzables de estas regiones primero, lo que generalmente produce una gran cantidad de espacio libre. Así que G1 recoge estas regiones (que contienen basura) primero, y de ahí el nombre Garbage-First. G1 también utiliza un modelo de predicción de pausa para cumplir con un objetivo de tiempo de pausa definido por el usuario. Selecciona el número de regiones a recoger según el objetivo de tiempo de pausa especificado. El ciclo de recolección de basura de G1 incluye las fases que se muestran en la figura:

  1. Fase solo joven: Esta fase incluye solo objetos de la generación joven y los promueve a la generación antigua. La transición entre la fase solo joven y la fase de reclamación de espacio comienza cuando la generación antigua está ocupada hasta cierto umbral, es decir, el umbral de ocupación inicial del montón. En este momento, G1 programa una recolección inicial solo joven en lugar de una recolección regular solo joven.

  2. Marca inicial: Este tipo de colección inicia el proceso de marcado además de una colección regular de solo jóvenes. El marcado concurrente determina todos los objetos actualmente activos en las regiones de la generación antigua que se mantendrán para la siguiente fase de reclamación de espacio. Mientras el marcado no haya terminado por completo, pueden ocurrir colecciones regulares de solo jóvenes. El marcado concluye con dos pausas especiales de detención total: Remark y Cleanup.

  3. Remark: Esta pausa finaliza el propio marcado y realiza el procesamiento global de referencias y la descarga de clases. Entre Remark y Cleanup, G1 calcula un resumen de la información de actividad de forma concurrente, que se finalizará y utilizará en la pausa Cleanup para actualizar las estructuras de datos internas.

  4. Cleanup: Esta pausa también toma las regiones completamente vacías y determina si realmente seguirá una fase de reclamación de espacio. Si sigue una fase de reclamación de espacio, la fase solo joven se completa con una única colección solo joven.

  5. Fase de reclamación de espacio: Esta fase consta de múltiples colecciones mixtas – además de las regiones de la generación joven, también evacua objetos vivos de las regiones de la generación antigua. La fase de reclamación de espacio finaliza cuando G1 determina que evacuar más regiones de la generación antigua no produciría suficiente espacio libre que valga la pena el esfuerzo.

G1 se puede habilitar utilizando la bandera -XX:+UseG1GC. Esta estrategia reduce las posibilidades de que el montón (heap) se agote antes de que los hilos de fondo hayan terminado de escanear los objetos inalcanzables. Además, compacta el montón sobre la marcha, algo que el recolector CMS solo puede hacer en modo STW. En Java 8 se proporciona una hermosa optimización con el recolector G1, llamada deduplicación de cadenas. Como sabemos, los arreglos de caracteres que representan nuestras cadenas ocupan gran parte de nuestro espacio en el montón. Se ha realizado una nueva optimización que permite al recolector G1 identificar cadenas que están duplicadas más de una vez en todo nuestro montón y modificarlas para que apunten al mismo arreglo interno de char[], evitando múltiples copias de la misma cadena que residen innecesariamente en el montón. Podemos usar el argumento de JVM -XX:+UseStringDeduplication para habilitar esta optimización. G1 es el recolector de basura predeterminado en JDK 9.

Java 8 PermGen y Metaspace

Como se mencionó anteriormente, el espacio de Generación Permanente fue eliminado desde Java 8. Entonces, ahora, el JVM HotSpot de JDK 8 utiliza la memoria nativa para la representación de metadatos de clase, que se llama Metaspace. La mayoría de las asignaciones para los metadatos de clase se realizan fuera de la memoria nativa. Además, hay una nueva bandera MaxMetaspaceSize, para limitar la cantidad de memoria utilizada para los metadatos de clase. Si no especificamos el valor para esto, el Metaspace se redimensiona en tiempo de ejecución según la demanda de la aplicación en ejecución. La recolección de basura de Metaspace se activa cuando el uso de metadatos de clase alcanza el límite de MaxMetaspaceSize. La recolección excesiva de basura de Metaspace puede ser un síntoma de fugas de memoria de clases, classloaders o un dimensionamiento inadecuado para nuestra aplicación. Eso es todo para la recolección de basura en java. Espero que hayas entendido acerca de los diferentes recolectores de basura que tenemos en java. Referencias: Documentación de Oracle, G1 GC.

Source:
https://www.digitalocean.com/community/tutorials/garbage-collection-in-java