Pular para o conteúdo principal

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
impacto

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