Comprender el modelo de memoria de la JVM y la gestión de memoria de Java son muy importantes si desea comprender el funcionamiento de la recolección de basura de Java . Hoy analizaremos la gestión de memoria en Java, las diferentes partes de la memoria de la JVM y cómo monitorear y realizar ajustes en la recolección de basura.
Modelo de memoria de Java (JVM)
Como puede ver en la imagen anterior, la memoria de la JVM está dividida en partes separadas. A grandes rasgos, la memoria del montón de la JVM está dividida físicamente en dos partes: generación joven y generación anterior .
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 está llena, se realiza la recolección de basura. Esta recolección de basura se llama Minor GC . La generación joven se divide en tres partes: Eden Memory y dos espacios Survivor Memory . Puntos importantes sobre los espacios de la generación joven:
- La mayoría de los objetos recién creados se encuentran en el espacio de memoria del Edén.
- Cuando el espacio Edén se llena de objetos, se realiza una GC menor y todos los objetos sobrevivientes se mueven a uno de los espacios sobrevivientes.
- Minor GC también comprueba los objetos supervivientes y los mueve al otro espacio de superviviente. De esta forma, en un momento dado, uno de los espacios de superviviente siempre está vacío.
- Los objetos que sobreviven después de muchos ciclos de recolección de basura se trasladan al espacio de memoria de la generación anterior. Por lo general, esto 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 anterior.
Gestión de memoria en Java: generación anterior
La memoria de la vieja generación contiene los objetos que tienen una larga vida útil y sobrevivieron 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 vieja generación cuando está llena. La recolección de basura de la vieja generación se denomina recolección de basura mayor y, por lo general, demora más tiempo.
Detener el evento mundial
Todas las recolecciones de basura son eventos de “Detener el mundo” porque todos los subprocesos de la aplicación se detienen hasta que se completa la operación. Dado que la generación joven conserva los objetos de corta duración, la recolección de basura menor es muy rápida y la aplicación no se ve afectada por esto. Sin embargo, la recolección de basura mayor lleva mucho tiempo porque verifica todos los objetos activos. La recolección de basura mayor debe minimizarse porque hará que su aplicación no responda durante la duración de la recolección de basura. Por lo tanto, si tiene una aplicación responsiva y hay muchas recolecciones de basura mayores en curso, notará errores de tiempo de espera. La duración que tarda el recolector de basura depende de la estrategia utilizada para la recolección de basura. Es por eso que es necesario monitorear y ajustar el recolector de basura para evitar tiempos de espera en las aplicaciones altamente responsivas.
Modelo de memoria Java: generación permanente
La generación permanente o “Perm Gen” contiene los metadatos de la aplicación que requiere la JVM para describir las clases y los métodos utilizados en la aplicación. Tenga en cuenta que Perm Gen no forma parte de la memoria del montón de Java. La JVM completa Perm Gen en tiempo de ejecución en función de las clases que utiliza la aplicación. Perm Gen también contiene clases y métodos de la biblioteca Java SE. Los objetos de Perm Gen se recolectan como basura en una recolección de basura completa.
Modelo de memoria de Java: área de métodos
El área de método es parte del espacio en Perm Gen y se utiliza para almacenar la estructura de clase (constantes de tiempo de ejecución y variables estáticas) y código para métodos y constructores.
Modelo de memoria de Java: grupo de memoria
Los administradores de memoria de JVM crean grupos de memoria para crear un grupo de objetos inmutables si la implementación lo admite. String Pool es un buen ejemplo de este tipo de grupo de memoria. El grupo de memoria puede pertenecer a Heap o Perm Gen, según la implementación del administrador de memoria de JVM.
Modelo de memoria de Java: grupo de constantes en tiempo de ejecución
El grupo de constantes de tiempo de ejecución es una representación en tiempo de ejecución por clase del grupo de constantes de una clase. Contiene constantes de tiempo de ejecución y métodos estáticos de clase. El grupo de constantes de tiempo de ejecución es parte del área de métodos.
Modelo de memoria de Java: memoria de pila de Java
La memoria de pila de Java se utiliza para la ejecución de un subproceso. Contiene valores específicos de métodos que son de corta duración y referencias a otros objetos en el montón a los que se hace referencia desde el método. Debe leer Diferencia entre memoria de pila y de montón .
Gestión de memoria en Java: conmutadores de memoria del montón de Java
Java ofrece una gran cantidad de opciones de memoria que podemos utilizar para establecer los tamaños de memoria y sus proporciones. Algunas de las opciones de memoria más utilizadas son:
| Conmutador de máquina virtual | Descripción del conmutador de VM |
|---|---|
| -Xms | Para establecer el tamaño del montón inicial cuando se inicia JVM |
| -Xmx | Para establecer el tamaño máximo del montón. |
| -Xmn | Para establecer el tamaño de la Generación Joven, el resto del espacio se destina a la Generación Vieja. |
| -XX:Generación permanente | Para establecer el tamaño inicial de la memoria de generación permanente |
| -XX:MaxPermGen | Para establecer el tamaño máximo de Perm Gen |
| -XX: Proporción de supervivientes | Para proporcionar la proporción entre el espacio de Edén y el espacio de supervivientes, por ejemplo, si el tamaño de la generación joven es de 10 m y el cambio de VM es -XX:SurvivorRatio=2, se reservarán 5 m para el espacio de Edén y 2,5 m para cada uno de los espacios de supervivientes. El valor predeterminado es 8. |
| -XX:Nueva proporción | Para proporcionar la relación entre el tamaño de la generación antigua y la nueva. El valor predeterminado es 2. |
La mayoría de las veces, las opciones anteriores son suficientes, pero si también desea consultar otras opciones, consulte la página oficial de opciones de JVM .
Gestión de memoria en Java: recolección de basura en 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 asignarlo a los objetos creados en el futuro procesamiento. 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 desasignación de memoria es un proceso manual. El recolector de basura es el programa que se ejecuta en segundo plano y que examina todos los objetos en la memoria y encuentra objetos a los que no hace referencia ninguna parte del programa. Todos estos objetos sin referencia se eliminan y se recupera espacio para asignarlo a otros objetos. Una de las formas básicas de recolección de basura implica tres pasos:
- Marcado : este es el primer paso en el que el recolector de basura identifica qué objetos están en uso y cuáles no.
- Eliminación normal : el recolector de basura elimina los objetos no utilizados y recupera 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 se pueden mover para que estén juntos. Esto aumentará el rendimiento de la asignación de memoria a los objetos más nuevos.
Hay dos problemas con un enfoque simple de marcar y eliminar.
- La primera es que no es eficiente porque la mayoría de los objetos recién creados quedarán sin uso.
- En segundo lugar, es más probable que los objetos que se utilizan en múltiples ciclos de recolección de basura también se utilicen en ciclos futuros.
Las deficiencias mencionadas anteriormente 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 anterior en la memoria del montón. Ya he explicado anteriormente cómo se escanean los objetos y se mueven de un espacio generacional a otro en función de la recolección de basura menor y la recolección de basura 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 utilizar en nuestras aplicaciones. Solo necesitamos usar el modificador JVM para habilitar la estrategia de recolección de basura para la aplicación. Veamos cada uno de ellos uno por uno.
- Recolección de basura en serie (-XX:+UseSerialGC) : la recolección de basura en serie utiliza el método simple de marcado, barrido y compactación para la recolección de basura de generaciones jóvenes y antiguas, es decir, recolección de basura menor y mayor. La recolección de basura en serie es útil en máquinas cliente, como nuestras aplicaciones independientes simples y máquinas con CPU más pequeñas. Es bueno para aplicaciones pequeñas con poca memoria.
- GC paralelo (-XX:+UseParallelGC) : el GC paralelo es igual que el GC serial, excepto que genera N subprocesos para la recolección de basura de la generación joven, donde N es la cantidad de núcleos de CPU en el sistema. Podemos controlar la cantidad de subprocesos usando
-XX:ParallelGCThreads=nla opción JVM. El recolector de basura paralelo también se denomina recolector de rendimiento porque usa varias CPU para acelerar el rendimiento del GC. El GC paralelo usa un solo subproceso para la recolección de basura de la generación anterior. - GC paralelo antiguo (-XX:+UseParallelOldGC) : es igual que GC paralelo excepto que utiliza múltiples subprocesos para la recolección de basura de la generación joven y de la generación anterior.
- Recopilador de barrido de marcas concurrente (CMS) (-XX:+UseConcMarkSweepGC) : el recolector CMS también se conoce como recolector de pausas bajas concurrente. Realiza la recolección de basura para la generación anterior. El recolector CMS intenta minimizar las pausas debido a la recolección de basura al realizar la mayor parte del trabajo de recolección de basura simultáneamente con los subprocesos de la aplicación. El recolector CMS en la generación joven usa el mismo algoritmo que el recolector paralelo. Este recolector de basura es adecuado para aplicaciones receptivas donde no podemos permitirnos tiempos de pausa más largos. Podemos limitar la cantidad de subprocesos en el recolector CMS usando
-XX:ParallelCMSThreads=nla opción JVM. - Recolector de basura G1 (-XX:+UseG1GC) : El recolector de basura Garbage First o G1 está disponible en Java 7 y su objetivo a largo plazo es reemplazar al recolector CMS. El recolector G1 es un recolector de basura paralelo, concurrente y de compactación incremental con pausas cortas. El recolector Garbage First no funciona como otros recolectores y no existe el concepto de espacio de generación joven y antiguo. Divide el espacio del montón en múltiples regiones de montón de igual tamaño. Cuando se invoca una recolección de basura, primero recolecta la región con menos datos en vivo, de ahí el término “Garbage First”. Puede encontrar más detalles al respecto en la documentación de Oracle sobre el recolector Garbage-First .
Gestión de memoria en Java: supervisión de la recolección de basura en Java
Podemos utilizar la línea de comandos de Java, así como las herramientas de interfaz de usuario, para supervisar las actividades de recolección de basura de una aplicación. En mi ejemplo, estoy utilizando una de las aplicaciones de demostración proporcionadas por las descargas de Java SE. Si desea utilizar la misma aplicación, vaya a la página de descargas de Java SE y descargue JDK 7 y JavaFX Demos and Samples . La aplicación de muestra que estoy utilizando es Java2Demo.jar y está presente en jdk1.7.0_55/demo/jfc/Java2Del directorio. Sin embargo, este es un paso opcional y puede ejecutar los comandos de supervisión de GC para cualquier aplicación Java. El comando utilizado por mí 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
estadísticas
Podemos usar jstatla herramienta de línea de comandos para monitorear la memoria de la JVM y las actividades de recolección de basura. Viene con el JDK estándar, por lo que no necesita hacer nada más para obtenerla. Para ejecutarla, jstatnecesita saber el ID del proceso de la aplicación, que puede obtener fácilmente mediante ps -eaf | grep javaun comando.
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 GCT1024.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.6541024.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.6541024.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.6541024.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.6541024.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.6541024.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.6561024.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 de jstat es el intervalo de tiempo entre cada salida, por lo que imprimirá los datos de memoria y recolección de basura cada 1 segundo. Repasemos 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. Observe que una de las áreas de Survivor está vacía todo el tiempo.
- CE y UE : estas columnas muestran el tamaño actual y el uso del espacio Eden en KB. Tenga en cuenta que el tamaño de la UE aumenta y, tan pronto como cruza la CE, se invoca un GC menor y el tamaño de la UE disminuye.
- OC y OU : estas columnas muestran el tamaño actual y el uso actual de la generación anterior en KB.
- PC y PU : estas columnas muestran el tamaño actual y el uso actual de Perm Gen en KB.
- YGC y YGCT : la columna YGC muestra la cantidad de eventos de GC ocurridos en la generación joven. La columna YGCT muestra el tiempo acumulado para las operaciones de GC para la generación joven. Observe que ambos se incrementan en la misma fila donde se reduce el valor de EU debido a un GC menor.
- FGC y FGCT : la columna FGC muestra la cantidad de eventos de GC completos que ocurrieron. La columna FGCT muestra el tiempo acumulado para las operaciones de GC completos. 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 las operaciones de recolección de basura. Observe que es la suma de los valores de las columnas YGCT y FGCT.
La ventaja de jstat es que también se puede ejecutar en servidores remotos donde no tenemos interfaz gráfica de usuario. Observe que la suma de S0C, S1C y EC es 10 m, como se especifica mediante -Xmn10mla opción JVM.
Java VisualVM con Visual GC
Si desea ver las operaciones de memoria y GC en la GUI, puede usar jvisualvmla herramienta. Java VisualVM también es parte de JDK, por lo que no necesita descargarlo por separado. Simplemente ejecute jvisualvmel comando en la terminal para iniciar la aplicación Java VisualVM. Una vez iniciada, debe instalar el complemento Visual GC desde la opción Herramientas – Complementos, como se muestra en la siguiente imagen. Después de instalar Visual GC , simplemente abra la aplicación desde la columna del lado izquierdo y diríjase a la sección Visual GC . Obtendrá una imagen de la memoria JVM y los detalles de recolección de basura como se muestra en la siguiente imagen.
Ajuste de la recolección de basura de Java
El ajuste de recolección de basura de Java debe ser la última opción que debe utilizar para aumentar el rendimiento de su aplicación y solo cuando vea una caída en el rendimiento debido a tiempos de GC más largos que causan el tiempo de espera de la aplicación. Si ve java.lang.OutOfMemoryError: PermGen spaceerrores en los registros, intente monitorear y aumentar el espacio de memoria de generación permanente utilizando las opciones -XX: PermGen y -XX: MaxPermGen de JVM. También puede intentar usar -XX:+CMSClassUnloadingEnabledy verificar cómo funciona con el recolector de basura de CMS. Si ve muchas operaciones de GC completas, debe intentar aumentar el espacio de memoria de la generación anterior. El ajuste general de la recolección de basura requiere mucho esfuerzo y tiempo y no existe una regla estricta para eso. Necesitaría probar diferentes opciones y compararlas para encontrar la mejor adecuada para su aplicación. Eso es todo sobre el modelo de memoria de Java, la administración de memoria en Java y la recolección de basura. Espero que lo ayude a comprender la memoria de JVM y el proceso de recolección de basura.