🐋 Suporte do Java SE para Limites de CPU e Memória do Docker
Antes do Java SE 8u131 e do Java 9, a JVM não reconhecia os limites de memória e CPU definidos pelo Docker. Isso significava que os parâmetros da JVM, como -Xmx (heap máximo), e o número de detectores de CPU (usados para dimensionar pools de threads internos, como o ForkJoinPool comum), eram definidos com base nos recursos da máquina host, e não nos limites configurados para o contêiner.
🚀 A partir do Java SE 8u131+ e do Java 9+
Nas versões Java SE 8u131+ e Java 9+, a JVM foi aprimorada para ser "sensível ao contêiner". Isso significa que, se o Java estiver sendo executado em um ambiente Linux com contêineres, ele examinará os limites de memória e CPU definidos para o contêiner e os usará para dimensionar os recursos do sistema.
Isso acontece por padrão sem a necessidade de nenhuma opção de linha de comando. No entanto, o comportamento pode ser sobrescrito para retornar ao modo anterior (baseado no host) usando a flag de linha de comando -XX:-UseContainerSupport.
Também é possível controlar explicitamente o heap máximo com a flag -XX:MaxRAMPercentage (por exemplo, -XX:MaxRAMPercentage=75.0).
📊 Calculando o Heap Máximo
Em geral, para configurações típicas de servidor, recomenda-se usar:
-XX:MaxRAMPercentage=75.0
Isso garante que o heap máximo seja definido como 75% da memória do contêiner, deixando espaço para outras estruturas de dados de tempo de execução, como Metaspace, código nativo, stacks de threads, etc.
Um exemplo de linha de comando para iniciar um aplicativo Java em um contêiner Docker poderia ser:
java -XX:MaxRAMPercentage=75.0 -jar MyApp.jar
🛑 Suporte para CPUs
Em contêineres Docker, há três configurações principais relacionadas à CPU que podem ser definidas:
- Número de CPUs:
--cpuset-cpusdefine em quais CPUs (núcleos) o contêiner pode ser executado. - Contagem de CPUs:
--cpu-sharesdefine uma porcentagem relativa (peso) da CPU. - Limite de CPU:
--cpus(uma opção mais recente) define um limite decimal para o número de CPUs (núcleos) que o contêiner pode usar.
A JVM agora leva essas configurações em consideração ao determinar o número de processadores disponíveis (ou seja, o valor retornado por Runtime.availableProcessors()).
Para Java 8u131+ e Java 9+, a JVM usará o número de CPUs definido por --cpuset-cpus (se especificado) ou o limite decimal de --cpus (arredondado para cima). O compartilhamento de CPU (--cpu-shares) não é mais usado para determinar a contagem de CPUs nessas versões.
⚙️ Comportamento nas Diferentes Versões do Java
O comportamento detalhado para detectar o número de CPUs varia entre as versões:
- Java SE 8u131+ e Java 9+: A detecção de CPUs é ativada por padrão. Pode ser desativada com
-XX:-UseContainerSupport. O número de CPUs é derivado de--cpuset-cpus(se configurado), caso contrário, de--cpus(arredondado para cima).--cpu-sharesé ignorado. - Java SE 8u191+ e Java 10+: A detecção de CPUs é ativada por padrão. Pode ser desativada com
-XX:-UseContainerSupport. As configurações são combinadas: primeiro, tenta-se usar--cpus; se não definido, tenta-se--cpuset-cpus; se ainda não definido, então--cpu-sharesé considerado. - Java SE 12+: A detecção de CPUs é ativada por padrão. Pode ser desativada com
-XX:-UseContainerSupport. A lógica é a mesma da versão 8u191+.
🎯 Resumo
Em resumo, as versões modernas do Java SE (8u131+ e 9+) são "conscientes do contêiner". Elas automaticamente detectam e respeitam os limites de memória e CPU impostos pelo Docker, permitindo uma execução mais eficiente e previsível dentro de ambientes conteinerizados. Os desenvolvedores têm opções para ajustar o comportamento, como definir a porcentagem de RAM usada para o heap.
Para obter a melhor experiência, é recomendável usar as versões mais recentes do Java dentro de contêineres Docker.