Máquina virtual Java: ajuste del GC
En el último capítulo, aprendimos sobre varios CG generacionales. En este capítulo, discutiremos cómo ajustar el GC.
Tamano de la pila
El tamaño de la pila es un factor importante en el rendimiento de nuestras aplicaciones Java. Si es demasiado pequeño, se llenará con frecuencia y, como resultado, el GC tendrá que recolectarlo con frecuencia. Por otro lado, si solo aumentamos el tamaño del montón, aunque es necesario recopilarlo con menos frecuencia, la duración de las pausas aumentaría.
Además, aumentar el tamaño del montón tiene una grave penalización en el sistema operativo subyacente. Al usar la paginación, el sistema operativo hace que nuestros programas de aplicación vean mucha más memoria de la que realmente está disponible. El sistema operativo gestiona esto utilizando algo de espacio de intercambio en el disco, copiando partes inactivas de los programas en él. Cuando se necesitan esas partes, el sistema operativo las vuelve a copiar del disco a la memoria.
Supongamos que una máquina tiene 8G de memoria y la JVM ve 16G de memoria virtual, la JVM no sabría que, de hecho, solo hay 8G disponibles en el sistema. Solo solicitará 16G al sistema operativo, y una vez que obtenga esa memoria, continuará usándola. El sistema operativo tendrá que intercambiar una gran cantidad de datos dentro y fuera, y esto es una gran penalización en el rendimiento del sistema.
Y luego vienen las pausas que ocurrirían durante la GC completa de dicha memoria virtual. Dado que el GC actuará en todo el montón para la recolección y compactación, tendrá que esperar mucho para que la memoria virtual se intercambie fuera del disco. En el caso de un recopilador concurrente, los subprocesos en segundo plano tendrán que esperar mucho para que los datos se copien desde el espacio de intercambio a la memoria.
Entonces, aquí surge la pregunta de cómo deberíamos decidir el tamaño óptimo del montón. La primera regla es nunca solicitar al sistema operativo más memoria de la que realmente está presente. Esto evitaría totalmente el problema del intercambio frecuente. Si la máquina tiene varias JVM instaladas y en ejecución, entonces la solicitud total de memoria de todas ellas combinadas es menor que la RAM real presente en el sistema.
Puede controlar el tamaño de la solicitud de memoria por parte de la JVM utilizando dos indicadores:
-XmsN - Controla la memoria inicial solicitada.
-XmxN - Controla la memoria máxima que se puede solicitar.
Los valores predeterminados de estos dos indicadores dependen del sistema operativo subyacente. Por ejemplo, para 64b JVM que se ejecutan en MacOS, -XmsN = 64M y -XmxN = mínimo de 1G o 1/4 de la memoria física total.
Tenga en cuenta que la JVM se puede ajustar entre los dos valores automáticamente. Por ejemplo, si nota que está sucediendo demasiada GC, seguirá aumentando el tamaño de la memoria siempre que sea inferior a -XmxN y se cumplan los objetivos de rendimiento deseados.
Si sabe exactamente cuánta memoria necesita su aplicación, puede configurar -XmsN = -XmxN. En este caso, la JVM no necesita calcular un valor "óptimo" del montón y, por lo tanto, el proceso de GC se vuelve un poco más eficiente.
Tamaños de generación
Puede decidir cuánto del montón desea asignar al YG y cuánto desea asignar al OG. Ambos valores afectan el rendimiento de nuestras aplicaciones de la siguiente manera.
Si el tamaño del YG es muy grande, entonces se recolectará con menos frecuencia. Esto resultaría en una menor cantidad de objetos promovidos al OG. Por otro lado, si aumenta demasiado el tamaño de OG, recolectarlo y compactarlo tomaría demasiado tiempo y esto conduciría a largas pausas de STW. Por lo tanto, el usuario debe encontrar un equilibrio entre estos dos valores.
A continuación se muestran las banderas que puede utilizar para establecer estos valores:
-XX:NewRatio=N: Relación de YG a OG (valor predeterminado = 2)
-XX:NewSize=N: Tamaño inicial de YG
-XX:MaxNewSize=N: Tamaño máximo de YG
-XmnN: Establezca NewSize y MaxNewSize en el mismo valor usando esta bandera
El tamaño inicial del YG está determinado por el valor de NewRatio mediante la fórmula dada:
(total heap size) / (newRatio + 1)
Dado que el valor inicial de newRatio es 2, la fórmula anterior da el valor inicial de YG como 1/3 del tamaño total del montón. Siempre puede anular este valor especificando explícitamente el tamaño del YG usando el indicador NewSize. Esta bandera no tiene ningún valor predeterminado, y si no se establece explícitamente, el tamaño del YG se seguirá calculando utilizando la fórmula anterior.
Permagen y Metaspace
El permagen y el metaspace son áreas de montón donde la JVM mantiene los metadatos de las clases. El espacio se llama 'permagen' en Java 7, y en Java 8, se llama 'metaespacio'. Esta información es utilizada por el compilador y el tiempo de ejecución.
Puedes controlar el tamaño del permagen usando las siguientes banderas: -XX: PermSize=N y -XX:MaxPermSize=N. El tamaño de Metaspace se puede controlar usando:-XX:Metaspace- Size=N y -XX:MaxMetaspaceSize=N.
Hay algunas diferencias en la forma en que se gestionan el permagen y el metaespacio cuando no se establecen los valores de las banderas. De forma predeterminada, ambos tienen un tamaño inicial predeterminado. Pero mientras que el metaespacio puede ocupar tanto del montón como sea necesario, el permagen no puede ocupar más que los valores iniciales predeterminados. Por ejemplo, la JVM 64b tiene 82M de espacio de almacenamiento dinámico como tamaño máximo de permagen.
Tenga en cuenta que dado que el metaespacio puede ocupar cantidades ilimitadas de memoria a menos que se especifique lo contrario, puede haber un error de memoria insuficiente. Se lleva a cabo una GC completa cada vez que se cambia el tamaño de estas regiones. Por lo tanto, durante el inicio, si hay muchas clases que se están cargando, el metaespacio puede seguir cambiando de tamaño dando como resultado un GC completo cada vez. Por lo tanto, las aplicaciones grandes necesitan mucho tiempo para iniciarse en caso de que el tamaño del metaespacio inicial sea demasiado bajo. Es una buena idea aumentar el tamaño inicial ya que reduce el tiempo de inicio.
Aunque el permagen y el metaspace contienen los metadatos de la clase, no es permanente y el GC recupera el espacio, como en el caso de los objetos. Esto suele ocurrir en el caso de aplicaciones de servidor. Siempre que realice una nueva implementación en el servidor, los metadatos antiguos deben limpiarse, ya que los nuevos cargadores de clases ahora necesitarán espacio. Este espacio es liberado por el GC.