JVM Ergonomics em Containers

Por Gaspar Barancelli Junior em 04 de abril de 2024

No JDK 5 foi introduzido um novo recurso chamado de JVM Ergonomics.

Este recurso obtém informações do sistema operacional e define automaticamente alguns valores padrões para a JVM e Garbage Collector. É uma forma de obter um bom desempenho em aplicativos java com um mínimo de ajustes por linha de comando.

Mas quando este recurso foi desenvolvido, as aplicações não rodavam em containers, por conta disso, a JVM Ergonomics não foi preparada para obter os dados dos recursos definidos no CGROUP, fazendo com que containers fossem reiniciados.

Para um melhor entendimento, vamos levar em conta uma máquina com 20GB de memória, mas com o container limitando a memória para 1GB, ao subir a aplicação a JVM Ergonomics vai levar em conta os 20GB da máquina e não do CGROUP, com isso, a aplicação poderia consumir mais de 1GB de memória, fazendo com que o container fosse reiniciado pois estaria passando do valor definido no container, que por fim entraria neste loop.

Mas logo foi lançado o update 191 do JDK 8 que resolveu este problema, fazendo com que a JVM Ergonomics obtenha os dados do CGROUP.

Customizar o máximo de alocação de memória

Utilizando a flag -Xmx podemos customizar o máximo de alocação de memória da JVM.

Portanto, caso você utilize uma versão do Java anterior a 8u191 em containers, atualize a versão do Java da sua aplicação, mas caso isso não seja possível, adicione a opção -Xmx onde o valor deve ser o mesmo atribuído ao limite máximo de memória do container, com isso o container não será mais reiniciado por conta da aplicação extrapolar o consumo de memória.

Segue um exemplo de utilização da flag -Xmx, onde atribuímos o valor máximo de alocação de memória para 2GB.

java -Xmx2G -jar blog.jar

Customizar o número de CPUs

A flag -XX:ActiveProcessorCount substitui o número de CPUs que a JVM detecta e usa para criar threads. Esta flag da JVM é equivalente a flag cpu_quota utilizada para limitar o tempo de CPU do container. Seus valores são atribuídos de formas diferentes, onde 1000m em cpu_quota corresponde a 1 proc em ActiveProcessorCount, 1001m a 2000m em cpu_quota equivale a 2 procs em ActiveProcessorCount e assim por diante.

A opção -cpu-quota diz respeito a configuração do CFS (Completely Fair Scheduler) que é o programa de CPU do kernel Linux.

O CFS é quem aloca recursos da CPU para executar processos, visando maximizar a utilização geral de CPU. É importante deixar claro que o CFS limita apenas o tempo de CPU e não o limite de CPU. Sendo assim, várias threads nativas podem ser executadas em paralelo até que a cota seja atingida.

Portanto, ao especificar um valor customizado para a opção -cpu-quota estamos alterando o número de microssegundos que um conteiner tem acesso aos recursos da CPU.

Customizar o Garbage Collector

Ao executar uma aplicação Java 9+ num ambiente que tenha menos de 1792 MB de memória disponível para a JVM, o SerialGC é Garbage Collector definido como padrão, mas para ambientes que tenha mais memória disponível o G1GC é o Garbage Collector definido.

Agora vem algumas provocações, pensando em escalonamento.

Será que faz sentido escalar uma aplicação horizontalmente quando definimos um limite de memória abaixo de 1792 MB para um container?

Definindo um limite maior de memória e utilizando um Garbage Collector mais adequado, a aplicação não teria uma menor pausa na coleta de lixo e por fim um melhor consumo de memória?

Faz sentido alterar o Garbage Collector, sendo que o JVM Ergonomics é quem define o padrão?

Infelizmente não temos uma resposta sobre qual a melhor estratégia de escalonamento e qual o melhor coletor, mas por meio de flags podemos alterar o coletor padrão da JVM, bem como tunar o coletor.

Abaixo segue algumas flags que alteram o coletor de lixo utilizado pela JVM.

java -XX:+UseSerialGC -jar blog.jar

java -XX:+UseParallelGC -jar blog.jar

java -XX:+UseParNewGC -jar blog.jar

java -XX:+UseG1GC -jar blog.java

// Compartilhe esse Post