Comprender el Modelo de Memoria JVM, la Gestión de Memoria en Java es muy importante si quieres entender el funcionamiento de la Recolección de Basura en Java. Hoy veremos la gestión de memoria en Java, las diferentes partes de la memoria JVM y cómo monitorear y realizar ajustes en la recolección de basura.
Modelo de Memoria Java (JVM)
Como puedes ver en la imagen anterior, la memoria JVM está dividida en partes separadas. A nivel amplio, la memoria del montón de la JVM está físicamente dividida en dos partes: Generación Joven y Generación Vieja.
Gestión de Memoria en Java – Generación Joven
La generación joven es el lugar donde se crean todos los objetos nuevos. Cuando la generación joven se llena, se realiza la recolección de basura. Esta recolección de basura se llama GC Menor. La generación joven está dividida en tres partes – Memoria Eden y dos espacios de Memoria de Sobreviviente. Puntos importantes sobre los espacios de Generación Joven:
- La mayoría de los objetos recién creados se encuentran en el espacio de memoria Eden.
- Cuando el espacio Eden se llena con objetos, se realiza una recolección de basura menor (Minor GC) y todos los objetos supervivientes se mueven a uno de los espacios de supervivencia.
- La recolección de basura menor también verifica los objetos supervivientes y los mueve al otro espacio de supervivencia. Por lo tanto, en un momento dado, uno de los espacios de supervivencia siempre está vacío.
- Los objetos que sobreviven después de muchas rondas de GC se mueven al espacio de memoria de la generación antigua. Por lo general, se hace estableciendo un umbral para la edad de los objetos de la generación joven antes de que sean elegibles para promoverse a la generación antigua.
Administración de memoria en Java – Generación Antigua
La memoria de la Generación Antigua contiene los objetos que tienen una vida útil prolongada y han sobrevivido después de muchas rondas de recolección de basura menor. Por lo general, la recolección de basura se realiza en la memoria de la Generación Antigua cuando está llena. La recolección de basura de la Generación Antigua se llama GC Mayor y generalmente lleva más tiempo.
Evento de Detención del Mundo (Stop the World)
Todas las recolecciones de basura son eventos de “detener el mundo” porque todos los hilos de la aplicación se detienen hasta que se completa la operación. Dado que la generación joven mantiene objetos de corta vida, la recolección menor es muy rápida y la aplicación no se ve afectada por esto. Sin embargo, la recolección mayor lleva mucho tiempo porque verifica todos los objetos vivos. Se debe minimizar la recolección mayor porque hará que su aplicación no responda durante la duración de la recolección de basura. Entonces, si tiene una aplicación receptiva y hay muchas recolecciones principales de basura ocurriendo, notará errores de tiempo de espera. La duración tomada por el recolector de basura depende de la estrategia utilizada para la recolección de basura. Por eso es necesario monitorear y ajustar el recolector de basura para evitar tiempos de espera en las aplicaciones altamente receptivas.
Modelo de memoria de Java – Generación Permanente
La Generación Permanente o “Perm Gen” contiene los metadatos de la aplicación requeridos por la JVM para describir las clases y métodos utilizados en la aplicación. Tenga en cuenta que Perm Gen no es parte de la memoria del montón de Java. Perm Gen es poblado por la JVM en tiempo de ejecución en función de las clases utilizadas por la aplicación. Perm Gen también contiene clases y métodos de la biblioteca SE de Java. Los objetos de Perm Gen son recolectados por el recolector de basura completo.
Modelo de memoria de Java – Área de método
El área de método es parte del espacio en Perm Gen y se utiliza para almacenar la estructura de clases (constantes en tiempo de ejecución y variables estáticas) y el código de métodos y constructores.
Modelo de memoria de Java – Grupo de memoria
Los grupos de memoria son creados por los administradores de memoria JVM para crear un grupo de objetos inmutables si la implementación lo admite. El grupo de cadenas (String Pool) es un buen ejemplo de este tipo de grupo de memoria. El grupo de memoria puede pertenecer a la Heap o a Perm Gen, dependiendo de la implementación del administrador de memoria JVM.
Modelo de memoria de Java – Pool de constantes de tiempo de ejecución
El pool de constantes de tiempo de ejecución es una representación en tiempo de ejecución por clase del pool de constantes en una clase. Contiene constantes en tiempo de ejecución de clase y métodos estáticos. El pool de constantes de tiempo de ejecución es parte del área de método.
Modelo de Memoria de Java – Memoria de la Pila de Java
La memoria de la pila de Java se utiliza para la ejecución de un hilo. Contienen valores específicos del método que son de corta duración y referencias a otros objetos en el montón que están siendo referidos desde el método. Deberías leer Diferencia entre la Memoria de la Pila y la de Montón.
Gestión de Memoria en Java – Cambios de Memoria de Montón de Java
Java proporciona una gran cantidad de cambios de memoria que podemos utilizar para establecer los tamaños de memoria y sus proporciones. Algunos de los cambios de memoria comúnmente utilizados son:
VM Switch | VM Switch Description |
---|---|
-Xms | For setting the initial heap size when JVM starts |
-Xmx | For setting the maximum heap size. |
-Xmn | For setting the size of the Young Generation, rest of the space goes for Old Generation. |
-XX:PermGen | For setting the initial size of the Permanent Generation memory |
-XX:MaxPermGen | For setting the maximum size of Perm Gen |
-XX:SurvivorRatio | For providing ratio of Eden space and Survivor Space, for example if Young Generation size is 10m and VM switch is -XX:SurvivorRatio=2 then 5m will be reserved for Eden Space and 2.5m each for both the Survivor spaces. The default value is 8. |
-XX:NewRatio | For providing ratio of old/new generation sizes. The default value is 2. |
La mayoría de las veces, las opciones anteriores son suficientes, pero si quieres consultar otras opciones también, por favor revisa la Página Oficial de Opciones de JVM.
Gestión de memoria en Java – Recolección de basura de Java
La recolección de basura de Java es el proceso para identificar y eliminar los objetos no utilizados de la memoria y liberar espacio para ser asignado a objetos creados en futuros procesamientos. Una de las mejores características del lenguaje de programación Java es la recolección automática de basura, a diferencia de otros lenguajes de programación como C donde la asignación y liberación de memoria es un proceso manual. El Recolector de basura es el programa que se ejecuta en segundo plano y examina todos los objetos en la memoria para encontrar objetos que no estén referenciados por ninguna parte del programa. Todos estos objetos no referenciados son eliminados y se reclama espacio para asignarlo a otros objetos. Uno de los métodos básicos de recolección de basura implica tres pasos:
- Marca: Este es el primer paso donde el recolector de basura identifica qué objetos están en uso y cuáles no lo están.
- Eliminación normal: El recolector de basura elimina los objetos no utilizados y reclama el espacio libre para asignarlo a otros objetos.
- Eliminación con compactación: Para un mejor rendimiento, después de eliminar objetos no utilizados, todos los objetos supervivientes pueden ser movidos para estar juntos. Esto aumentará el rendimiento de la asignación de memoria a objetos más nuevos.
Hay dos problemas con un enfoque simple de marcar y eliminar.
- En primer lugar, no es eficiente porque la mayoría de los objetos recién creados quedarán sin usar.
- En segundo lugar, los objetos que están en uso durante múltiples ciclos de recolección de basura probablemente seguirán en uso para ciclos futuros también.
Las deficiencias anteriores con el enfoque simple son la razón por la que la Recolección de Basura de Java es Generacional y tenemos espacios de Generación Joven y Generación Antigua en la memoria heap. Ya expliqué anteriormente cómo se escanean y se mueven los objetos de un espacio generacional a otro basado en la GC Menor y la GC Mayor.
Gestión de Memoria en Java – Tipos de Recolección de Basura en Java
Existen cinco tipos de recolección de basura que podemos usar en nuestras aplicaciones. Solo necesitamos utilizar el interruptor de la JVM para habilitar la estrategia de recolección de basura para la aplicación. Veamos cada uno de ellos uno por uno.
- GC en Serie (-XX:+UseSerialGC): GC en Serie utiliza el enfoque marcar-barrer-compac simple para la recolección de basura de las generaciones joven y antigua, es decir, GC Menor y GC Mayor. GC en Serie es útil en máquinas cliente como nuestras simples aplicaciones independientes y máquinas con CPU más pequeñas. Es bueno para aplicaciones pequeñas con bajo consumo de memoria.
- GC Paralelo (-XX:+UseParallelGC): El GC Paralelo es igual que el GC Serial excepto que genera N hilos para la recolección de basura de la generación joven, donde N es el número de núcleos de CPU en el sistema. Podemos controlar el número de hilos usando la opción de JVM
-XX:ParallelGCThreads=n
. El Recolector de Basura Paralelo también se llama recolector de rendimiento porque utiliza múltiples CPUs para acelerar el rendimiento del GC. El GC Paralelo utiliza un solo hilo para la recolección de basura de la Generación Antigua. - GC Paralelo Antiguo (-XX:+UseParallelOldGC): Esto es igual que el GC Paralelo excepto que utiliza múltiples hilos tanto para la recolección de basura de la Generación Joven como de la Generación Antigua.
- Recolector de Barrido Concurrente (CMS) (-XX:+UseConcMarkSweepGC): El Recolector CMS también se conoce como recolector de bajo tiempo de espera concurrente. Realiza la recolección de basura para la Generación Antigua. El recolector CMS intenta minimizar las pausas debido a la recolección de basura realizando la mayor parte del trabajo de recolección de basura de manera concurrente con los hilos de la aplicación. El recolector CMS en la generación joven utiliza el mismo algoritmo que el recolector paralelo. Este recolector de basura es adecuado para aplicaciones sensibles donde no podemos permitir tiempos de pausa más largos. Podemos limitar el número de hilos en el recolector CMS usando la opción de JVM
-XX:ParallelCMSThreads=n
. - Recolector de basura G1 (-XX:+UseG1GC): El recolector de basura Garbage First o G1 está disponible desde Java 7 y su objetivo a largo plazo es reemplazar al recolector CMS. El recolector G1 es un recolector de basura paralelo, concurrente e incrementalmente compactador de baja pausa. El recolector Garbage First no funciona como otros recolectores y no hay concepto de espacio de generación Joven y Vieja. Divide el espacio de la pila en múltiples regiones de igual tamaño. Cuando se invoca una recolección de basura, primero recoge la región con menos datos vivos, de ahí “Garbage First”. Puedes encontrar más detalles al respecto en Documentación de Oracle sobre el recolector Garbage-First.
Gestión de memoria en Java – Monitoreo de recolección de basura en Java
Podemos utilizar la línea de comandos de Java así como herramientas de interfaz de usuario para monitorear las actividades de recolección de basura de una aplicación. Para mi ejemplo, estoy utilizando una de las aplicaciones de demostración proporcionadas por las descargas de Java SE. Si deseas utilizar la misma aplicación, ve a la página de Descargas de Java SE y descarga JDK 7 y Demostraciones y Ejemplos de JavaFX. La aplicación de ejemplo que estoy utilizando es Java2Demo.jar y está presente en el directorio jdk1.7.0_55/demo/jfc/Java2D
. Sin embargo, este es un paso opcional y puedes ejecutar los comandos de monitoreo de GC para cualquier aplicación Java. El comando que utilizo para iniciar la aplicación de demostración es:
pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
jstat
Podemos utilizar la herramienta de línea de comandos jstat
para monitorear la memoria de la JVM y las actividades de recolección de basura. Viene incluida con el JDK estándar, así que no necesitas hacer nada más para obtenerla. Para ejecutar jstat
necesitas conocer el ID del proceso de la aplicación, puedes obtenerlo fácilmente usando el comando ps -eaf | grep java
.
pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar
501 9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
501 14073 14045 0 9:48PM ttys002 0:00.00 grep Java2Demo.jar
Entonces, el ID del proceso para mi aplicación Java es 9582. Ahora podemos ejecutar el comando jstat como se muestra a continuación.
pankaj@Pankaj:~$ jstat -gc 9582 1000
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
El último argumento para jstat es el intervalo de tiempo entre cada salida, por lo que imprimirá datos de memoria y recolección de basura cada 1 segundo. Veamos cada una de las columnas una por una.
- S0C y S1C: Esta columna muestra el tamaño actual de las áreas Survivor0 y Survivor1 en KB.
S0U y S1U : Esta columna muestra el uso actual de las áreas Survivor0 y Survivor1 en KB. Observa que una de las áreas de sobrevivientes está vacía todo el tiempo. - S0U y S1U: Esta columna muestra el uso actual de las áreas Survivor0 y Survivor1 en KB. Tenga en cuenta que siempre hay un área de supervivencia vacía.
- EC y EU: Estas columnas muestran el tamaño actual y el uso del espacio Eden en KB. Tenga en cuenta que el tamaño de EU está aumentando y tan pronto como cruza el EC, se llama a GC menor y el tamaño de EU disminuye.
- OC y OU: Estas columnas muestran el tamaño actual y el uso actual de la generación antigua en KB.
- PC y PU: Estas columnas muestran el tamaño actual y el uso actual del Perm Gen en KB.
- YGC y YGCT: La columna YGC muestra el número de eventos de GC que ocurrieron en la generación joven. La columna YGCT muestra el tiempo acumulado para operaciones de GC en la generación joven. Tenga en cuenta que ambos aumentan en la misma fila donde el valor de EU cae debido a GC menor.
- FGC y FGCT: La columna FGC muestra el número de eventos de GC completo ocurridos. La columna FGCT muestra el tiempo acumulado para operaciones de GC completo. Tenga en cuenta que el tiempo de GC completo es demasiado alto en comparación con los tiempos de GC de la generación joven.
- GCT: Esta columna muestra el tiempo total acumulado para operaciones de GC. Tenga en cuenta que es la suma de los valores de las columnas YGCT y FGCT.
La ventaja de jstat es que se puede ejecutar en servidores remotos también donde no tenemos GUI. Tenga en cuenta que la suma de S0C, S1C y EC es 10m según lo especificado a través de la opción JVM -Xmn10m
.
Java VisualVM con Visual GC
Si desea ver las operaciones de memoria y GC en la GUI, entonces puede usar la herramienta jvisualvm
. Java VisualVM también es parte del JDK, por lo que no necesita descargarlo por separado. Simplemente ejecute el comando jvisualvm
en la terminal para abrir la aplicación Java VisualVM. Una vez abierta, necesitará instalar el complemento Visual GC desde la opción Herramientas -< Complementos, como se muestra en la imagen a continuación. Después de instalar Visual GC, simplemente abra la aplicación desde la columna izquierda y vaya a la sección Visual GC. Obtendrá una imagen de la memoria de la JVM y los detalles de la recolección de basura, como se muestra en la imagen a continuación.
Ajuste de la Recolección de Basura en Java
La sintonización de la recolección de basura de Java debería ser la última opción que deberías usar para aumentar el rendimiento de tu aplicación y solo cuando veas una disminución en el rendimiento debido a tiempos de GC más largos que causan que la aplicación se exceda del tiempo de espera. Si ves errores de java.lang.OutOfMemoryError: espacio PermGen
en los registros, entonces intenta monitorear y aumentar el espacio de memoria Perm Gen usando las opciones de JVM -XX:PermGen y -XX:MaxPermGen. También podrías intentar usar -XX:+CMSClassUnloadingEnabled
y verificar cómo se está desempeñando con el recolector de basura CMS. Si ves muchas operaciones de GC completo, entonces deberías intentar aumentar el espacio de memoria de la generación antigua. En general, la sintonización de la recolección de basura requiere mucho esfuerzo y tiempo y no hay reglas fijas para eso. Tendrías que probar diferentes opciones y compararlas para encontrar la mejor adecuada para tu aplicación. Eso es todo para el Modelo de Memoria de Java, la Gestión de Memoria en Java y la Recolección de Basura, espero que te ayude a comprender la memoria JVM y el proceso de recolección de basura.
Source:
https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java