Compartir vía


Administración de memoria Java

Nota:

Los planes Básico, Estándar y Enterprise quedarán en desuso a partir de mediados de marzo de 2025, con un período de jubilación de 3 años. Se recomienda realizar la transición a Azure Container Apps. Para obtener más información, consulte el anuncio de retirada de Azure Spring Apps.

El plan de consumo estándar y dedicado quedará obsoleto a partir del 30 de septiembre de 2024, con un cierre completo al cabo de seis meses. Se recomienda realizar la transición a Azure Container Apps. Para más información, consulte Migrar el consumo estándar de Azure Spring Apps y el plan dedicado a Azure Container Apps.

Este artículo se aplica a: ✔️ Nivel Básico o Estándar ❌ Nivel Enterprise

En este artículo se describen varios conceptos relacionados con la administración de memoria de Java para ayudarle a comprender el comportamiento de las aplicaciones Java hospedadas en Azure Spring Apps.

Modelo de memoria de Java

La memoria de una aplicación Java tiene varias partes y hay diferentes maneras de dividir las partes. En este artículo se describe la memoria de Java, que se divide en memoria en montón, memoria sin montón y memoria directa.

Memoria en montón

La memoria en montón almacena todas las instancias de clase y matrices. Cada máquina virtual Java (JVM) tiene solo un área en montón, que se comparte entre subprocesos.

El accionador de Spring Boot puede observar el valor de la memoria en montón. El accionador de Spring Boot toma el valor del montón como parte de jvm.memory.used/committed/max. Para más información, consulte la sección jvm.memory.used/committed/max en Herramientas para solucionar problemas de memoria.

La memoria del montón se divide en la generación joven y la generación antigua. Estos términos se describen en la lista siguiente, junto con los términos relacionados.

  • Generación joven: todos los objetos nuevos se asignan y envejecen en la generación joven.

    • Espacio del edén: se asignan nuevos objetos en el espacio del edén.
    • Espacio superviviente: los objetos se moverán del edén al espacio superviviente después de sobrevivir a un ciclo de recolección de elementos no utilizados. El espacio superviviente se puede dividir en dos partes: s1 y s2.
  • Generación antigua: también denominada espacio de antigüedad. Los objetos que han permanecido en los espacios supervivientes durante mucho tiempo se moverán a la generación antigua.

Antes de Java 8, había otra sección denominada generación permanente que también formaba parte del montón. A partir de Java 8, la generación permanente se reemplazó por el metaespacio en memoria sin montón.

Memoria sin montón

La memoria sin montón se divide en las siguientes partes:

  • La parte de la memoria sin montón que reemplazó a la generación permanente (o permGen) a partir de Java 8. El accionador de Spring Boot observa esta sección y la toma como parte de jvm.memory.used/committed/max. En otras palabras, jvm.memory.used/committed/max es la suma de la memoria en montón y de la antigua parte de permGen de la memoria sin montón. La antigua generación permanente se compone de las siguientes partes:

    • Metaespacio, que almacena las definiciones de clase cargadas por cargadores de clases.
    • Espacio de clase comprimido, que es para punteros de clase comprimidos.
    • Caché de código, que almacena código nativo compilado por JIT.
  • Otra memoria, como la pila de subprocesos, que no observa el accionador de Spring Boot.

Memoria directa

La memoria directa es la memoria nativa asignada por java.nio.DirectByteBuffer, que se usa en bibliotecas de terceros como nio y gzip.

El accionador de Spring Boot no observa el valor de la memoria directa.

En el diagrama siguiente se resume el modelo de memoria de Java descrito en la sección anterior.

Diagrama que muestra el modelo de memoria de Java.

Recolección de elementos no utilizados de Java

Hay tres términos con respecto a la recolección de elementos no utilizados (GC) de Java: "GC secundaria", "GC principal" y "GC completa". Estos términos no se definen claramente en la especificación de JVM. Aquí, consideramos que "GC principal" y "GC completa" son equivalentes.

La GC secundaria se realiza cuando el espacio del edén está lleno. Quita todos los objetos muertos de la generación joven y mueve los objetos dinámicos del espacio del edén a s1 del espacio superviviente, o de s1 a s2.

La GC completa o GC principal realiza la recolección de elementos no utilizados en todo el montón. La GC completa también puede recopilar elementos como el metaespacio y la memoria directa, que solo se pueden limpiar mediante la GC completa.

El tamaño máximo del montón influye en la frecuencia de la GC secundaria y la GC completa. El metaespacio máximo y el tamaño máximo de memoria directa influyen en la GC completa.

Al establecer el tamaño máximo del montón en un valor inferior, las recolecciones de elementos no utilizados se producen con más frecuencia, lo que ralentiza un poco la aplicación, pero limita mejor el uso de memoria. Cuando se establece el tamaño máximo del montón en un valor mayor, las recolecciones de elementos no utilizados se producen con menos frecuencia, lo que puede crear un mayor riesgo de memoria insuficiente (OOM). Para obtener más información, consulte la sección Tipos de problemas de memoria insuficiente de Problemas de reinicio de la aplicación causados por problemas de memoria insuficiente.

El metaespacio y la memoria directa solo se pueden recopilar mediante la GC completa. Cuando el metaespacio o la memoria directa estén llenas, se producirá una GC completa.

Configuraciones de memoria de Java

En las secciones siguientes se describen aspectos importantes de la configuración de memoria de Java.

Contenedorización de Java

Las aplicaciones de Azure Spring Apps se ejecutan en entornos de contenedor. Para obtener más información, consulte Incluir las aplicaciones Java en contenedores.

Opciones importantes de JVM

Puede configurar el tamaño máximo de cada parte de memoria mediante las opciones de JVM. Puede establecer las opciones de JVM mediante comandos de la CLI de Azure o a través de Azure Portal. Para obtener más información, consulte la sección Modificar configuraciones para corregir problemas de Herramientas para solucionar problemas de memoria.

En la lista siguiente se describen las opciones de JVM:

  • Configuración del tamaño del montón

    • -Xms establece el tamaño inicial del montón por valor absoluto.
    • -Xmx establece el tamaño máximo del montón por valor absoluto.
    • -XX:InitialRAMPercentage establece el tamaño inicial del montón según el porcentaje de tamaño del montón/el tamaño de memoria de la aplicación.
    • -XX:MaxRAMPercentage establece el tamaño máximo del montón según el porcentaje de tamaño del montón/el tamaño de memoria de la aplicación.
  • Configuración de tamaño de memoria directa

    • -XX:MaxDirectMemorySize establece el tamaño máximo de memoria directa por valor absoluto. Para obtener más información, consulte MaxDirectMemorySize en la documentación de Oracle.
  • Configuración del tamaño del metaespacio

    • -XX:MaxMetaspaceSize establece el tamaño máximo del metaespacio por valor absoluto.

Tamaño máximo predeterminado de memoria

En las secciones siguientes se describe cómo se establecen los tamaños máximos predeterminados de memoria.

Tamaño máximo predeterminado del montón

Azure Spring Apps establece el tamaño máximo predeterminado de memoria en montón en aproximadamente el 50 %-80 % de la memoria de la aplicación para aplicaciones Java. En concreto, Azure Spring Apps usa la siguiente configuración:

  • Si la memoria de la aplicación < 1 GB, el tamaño máximo predeterminado del montón será el 50 % de la memoria de la aplicación.
  • Si 1 GB <= la memoria de la aplicación < 2 GB, el tamaño máximo predeterminado del montón será el 60 % de la memoria de la aplicación.
  • Si 2 GB <= la memoria de la aplicación < 3 GB, el tamaño máximo predeterminado del montón será el 70 % de la memoria de la aplicación.
  • Si 3 GB <= la memoria de la aplicación, el tamaño máximo predeterminado del montón será el 80 % de la memoria de la aplicación.

Tamaño máximo predeterminado de memoria directa

Cuando el tamaño máximo de memoria directa no se establece mediante las opciones de JVM, la JVM establece automáticamente el tamaño máximo de memoria directa en el valor devuelto por Runtime.getRuntime.maxMemory(). Este valor es aproximadamente igual al tamaño máximo de memoria en montón. Para más información, consulte el archivo VM.java de JDK 8.

Diseño de uso de memoria

El tamaño del montón se ve afectado por el rendimiento. Básicamente, al configurar, puede mantener el tamaño máximo predeterminado del montón, que deja una memoria razonable para otras partes.

El tamaño del metaespacio depende de la complejidad del código, como el número de clases.

El tamaño de memoria directa depende del rendimiento y del uso de bibliotecas de terceros como nio y gzip.

En la lista siguiente se describe un ejemplo de diseño de memoria típico para aplicaciones de 2 GB. Puede consultar esta lista para configurar los valores de tamaño de memoria.

  • Memoria total (2048M)
  • Memoria en montón: Xmx es 1433,6M (70 % de memoria total). El valor de referencia del uso diario de memoria es de 1200M.
    • Generación joven
      • Espacio superviviente (S0, S1)
      • Espacio del edén
    • Generación antigua
  • Memoria sin montón
    • Parte observada (observada por el accionador de Spring Boot)
      • Metaespacio: el valor de referencia de uso diario es 50M-256M
      • Caché de código
      • Espacio de clases comprimido
    • Parte no observada (no observada por el accionador de Spring Boot): el valor de referencia de uso diario es 150M-250M.
      • Pila de los subprocesos
      • GC, símbolo interno y otros
  • Memoria directa: el valor de referencia de uso diario es 10M-200M.

En el diagrama siguiente se muestra la misma información. Los números en gris son los valores de referencia del uso diario de memoria.

Diagrama del diseño de memoria típico para aplicaciones de 2 GB.

En general, al configurar tamaños máximos de memoria, debe tener en cuenta el uso de cada parte en la memoria y la suma de todos los tamaños máximos no debe superar el total de memoria disponible.

OOM en Java

OOM significa que la aplicación no tiene memoria. Hay dos conceptos diferentes: OOM en contenedor y OOM en JVM. Para obtener más información, consulte Problemas de reinicio de la aplicación causados por problemas de memoria insuficiente.

Consulte también