Garbage Collector
Selecionando o GC
A coleta de lixo é um dos subsistemas gerenciados pela JVM, e seu impacto no desempenho é considerável caso não seja configurado adequadamente. Caso não realize nenhuma configuração, o processo para seleção será o Java Ergonomics.
Temos vários algoritmos disponíveis na JVM (pode variar conforme a versão do Java):
# para habilitar SerialGC
-XX:+UseSerialGC
# para habilitar ParallelGC
-XX:+UseParallelGC
# para habilitar G1GC
-XX:+UseG1GC
# para habilitar ZGC
-XX:+UseZGC -XX:+ZGenerational
Qual GC escolher?
SerialGC é um coletor que deve evitar em todos os casos, são raros (extremamente raros mesmo) os casos que o SerialGC terá um desempenho ideal. Todas suas coletas serão STW (parando todos os threads de aplicação / congelando a aplicação) e será usado apenas um core para a tarefa de limpeza, resultando em tempos muito ruins.
ParallelGC é um coletor paralelo que também terá todas suas coletas STW, porém, utilizará mais cores para a limpeza, tendo assim tempos melhores. É um dos melhores coletores para o Java 8, até mesmo melhor que o G1GC (vale ressaltar que o G1GC presente no Java 8 não é o mesmo presente no Java 11), porém, essa afirmação não é verdadeira para versões posteriores (+11).
Para a maioria dos casos utilize o G1GC ou ZGC, eles são mais novos e possuem formas mais avançadas e performáticas de lidar com o heap. Também são as principais recomendações para aplicações que possuem requisitos de tempo (onde as operações devem estar na casa dos milissegundos. Aplicações Rest, por exemplo).
Minha recomendação é iniciar com o G1GC e alterar caso sinta necessidade.
Você pode conferir mais detalhes na documentação oficial
GC Threads
A coleta de lixo pode ser separada em dois momentos:
- marcação: processo onde a JVM marcará quais objetos estão sendo utilizados / estão vivos.
- varredura: processo onde a JVM eliminará todos os objetos que não foram marcados / estão mortos.
Para alterar o número de threads paralelos que serão utilizados na fase de marcação, utilize:
-XX:ConcGCThreads=n
Para alterar o número de threads paralelos que serão utilizados na fase de varredura, utilize:
-XX:ParallelGCThreads=n
Aumentar a quantidade de threads irá melhorar o desempenho da sua aplicação, em troca, terá um uso mais elevado de CPU. Para todos os casos, utilize testes de stress para validar sua configuração.
Tempo de pausa - pause goals
Para informar ao GC quanto tempo ele deve levar em cada coleta, utilize:
-XX:MaxGCPauseTimeMillis=MILISEGUNDOS
É importante ressaltar que este parâmetro não fará o coletor pausar exatamente por tempo X, apenas definimos uma meta, o coletor buscará formas de atingí-la. O valor padrão gira em torno de 200ms, um valor extremamente alto para aplicações comuns (em torno de 1ms a 5ms, pela minha experiência).
Alterando tamanho das regiões do G1GC
Por padrão o G1GC "quebra" a mémoria em várias regiões, essa estratégia permite o GC paralelizar seu trabalho, atuando sobre uma região sem afetar outra.
Para alterar o tamanho da região, utilize:
-XX:G1HeapRegionSize=TAMANHO
Desabilitando o GC explicito - System.gc()
Algumas aplicações mal desenvolvidas podem invocar o método "System.gc()", afetando diretamente a sua aplicação, já que este método dispara uma coleta completa (pausando a aplicação por completo).
Para nos previnir desse comportamento, podemos desabilitar este método utilizando a tag:
-XX:+DisableExplicitGC
A recomendação é que você desabilite completamente o GC explicito da sua aplicação, mas caso ainda queira esse comportamento, podemos amenizá-lo utilizando a tag:
-XX:+ExplicitGCInvokesConcurrent
Ela converterá a coleta completa (que causa a pausa completa da aplicação) para uma coleta concorrente que rodará em paralelo aos threads da aplicação.
G1GC em containers de nucleo único
Tenha cuidado ao utilizar o G1GC em ambientes onde há apenas um core disponível. Mesmo habilitando explicitamente o G1GC, por conta da limitação de hardware, algumas JVMs (variando da distribuição) podem retornar um SerialGC no lugar.
Dentro do kubernetes é possível contornar isso adicionar 1m ao request do container:
requests:
cpu: "1001m"
G1GC - resolvendo excesso de coletas completas
Caso a JVM esteja realizando muitas coletas completas, podemos ajustar o limite do IHOP. Isso é feito aumentando o tamanho do buffer usado no cálculo do IHOP:
-XX:G1ReservePercent=percentual