Ejecutamos varios servicios Java (Corretto JDK21) en AWS Elastic Container Service (ECS) Fargate. Cada servicio tiene su propio contenedor y queremos utilizar todos los recursos posibles que estamos pagando por cada proceso. Pero los pasos se pueden aplicar a EC2 y otras nubes.
Los servicios ejecutan trabajos por lotes y la latencia no es importante, usamos Parallel GC (-XX: UseParallelGC). Quizás G1 sería mejor incluso con nuestras tareas, pero es un tema para investigación y publicación por separado.
Para utilizar toda la memoria disponible, MaxHeapSize es un poco menor que el tamaño de la memoria del contenedor. Pero después de un tiempo notamos dos problemas: a veces nuestros contenedores se eliminaban porque usaban demasiada memoria y a veces recibimos excepciones OutOfMemoryError. Para solucionar el primero, aumentamos la brecha entre el tamaño de la memoria del contenedor y MaxHeapSize y, para el segundo, aumentamos la memoria de los contenedores como una solución rápida y comenzamos a analizar los volcados del montón.
Los volcados de montón mostraron detalles interesantes, el tamaño real del montón era menor que MaxHeapSize y el montón de la generación joven era pequeño en comparación con la generación anterior.
Buscar en Internet no ayudó a encontrar una buena guía sobre cómo ajustar los parámetros de JVM para nuestro caso, solo encontré algunos detalles de alto nivel sobre montones y descripciones de parámetros. Decidí escribir esta publicación para describir los pasos que seguí.
Los primeros pasos fueron:
La tasa predeterminada para las generaciones jóvenes: mayores es 1: 2, y solo una parte de la generación joven se usa al mismo tiempo para realizar GC. Y después del inicio, JVM asignó toda la memoria como se esperaba, pero después de un tiempo comenzó a disminuir el tamaño del montón de Young Generation casi a varios megabytes. Entonces, después de un tiempo, usamos solo ⅔ de la memoria disponible.
Después de investigar un poco, encontré un parámetro para deshabilitar la Política adaptativa (-XX: -UseAdaptiveSizePolicy) y me ayudó, el montón dejó de disminuir y los intervalos entre recolecciones de basura aumentaron en un orden de magnitud o incluso más. El tiempo consumido por GC también aumentó, pero no tanto.
El siguiente paso fue encontrar la brecha óptima entre el tamaño de la memoria del contenedor. De forma predeterminada, incluso si InitialRAMPercentage=100, JDK simplemente asigna memoria y no la usa, por lo que no se asigna. Linux le permite asignar más memoria virtual que memoria física. Y el contenedor falla más tarde, cuando la memoria está realmente asignada (JDK escribe en ella). -XX: AlwaysPreTouch cambia este comportamiento. Desafortunadamente, parte de la memoria aún no está asignada, pero la terminación de OOM ocurre mucho más rápido. Después de varios intentos, terminé con la siguiente fórmula Tamaño de memoria del contenedor: 1024 MB para contenedores con 8 GB de memoria o más. Por ejemplo, para un tamaño de memoria de contenedor 8192 usamos -XX:MaxHeapSize=7168m.
Para optimizaciones adicionales, estamos pensando en cambiar -XX:NewRatio para disminuir el tamaño de la generación joven y reducir el tiempo de GC. Pero depende de la vida útil del objeto en la aplicación.
Como mencioné antes, no he encontrado ninguna buena guía con una explicación detallada de los parámetros (la mejor que encontré es vm-options-explorer) y pasos de ajuste. Sería fantástico si pudieras compartir tus conocimientos y resultados.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3