Garbage Collector
Selecionando o GC
GC é um dos subsistemas gerenciados pela JVM, sua principal função é limpar a mémoria da aplicação. Seu impacto no desempenho é considerável se não for configurado adequadamente, e caso não realize nenhuma configuração, o processo para seleção será o Java Ergonomics.
Temos vários GCs 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 devemos evitar em 99% dos casos, são raros (muito raros mesmo) os casos que o SerialGC terá um desempenho ideal. Todas suas operações são STW (parando todos os threads de aplicação / congelando a aplicação) e apenas um thread é utilizado nesse processo.
ParallelGC não vai muito longe do que o SerialGC é, a diferença é que ele consegue utilizar mais threads. Mas apesar disso, ele é 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).
Caso trabalhe com Java 11+ utilize 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).
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 - GC Roots.
- varredura: processo onde a JVM eliminará a memória não utilizada.
Para alterar o número de threads que serão utilizados na fase de marcação, utilize:
-XX:ConcGCThreads=n
Para alterar o número de threads 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/201ms, que é um valor bem alto, normalmente uma coleta do G1GC varia de 1ms a 5ms (saudável e sem degradação).
Você deve ter cuidado ao diminuir o valor padrão. Se definir um valor muito baixo, acabará com o coletor realizando muitas coletas para limpar o heap.
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 opção:
-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 opção:
-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.
Mesmo que este método possa causar uma coleta completa, nem sempre ocorrerá, veja o que diz a especificação da JVM: "the JVM still performs GC when necessary", ou seja, só fará quando considerar necessário.
Esse comportamento também pode variar conforme a distribuição da JVM e sua versão, veja o exemplo do Zing que varia não só por ser outra distribuição, mas por versão também.
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
- IHOP