A coleta de lixo é um recurso fundamental para desenvolvedores que criam e compilam programas em Java em uma Java Máquina Virtual, ou JVM. Os objetos em Java são criados no heap, que é uma seção de memória dedicada a um programa. Quando os objetos não são mais necessários, o coletor de lixo encontra e rastreia esses objetos não utilizados, e os exclui para liberar espaço. Sem a coleta de lixo, o heap acabaria ficando sem memória, levando a um OutOfMemoryError em tempo de execução.

A coleta de lixo em Java ajuda os seus ambientes e aplicações em Java a funcionar com mais eficiência. No entanto, você ainda pode encontrar problemas com a coleta automática de lixo, incluindo baixo desempenho do aplicações. Por mais que não seja possível substituir manualmente a coleta de lixo automática, há coisas que você pode fazer para otimizar a coleta de lixo na aplicações, como alterar o coletor de lixo, remover todas as referências a objetos em Java não utilizadas, e usar uma ferramenta de monitoramento de aplicações para otimizar o desempenho e detectar problemas assim que eles surgirem.

Os princípios básicos da coleta de lixo em Java

A coleta de lixo em Java é o processo automatizado de exclusão de código que não é mais necessário ou usado. Isso libera automaticamente espaço de memória e, idealmente, torna a codificação de aplicações em Java mais fácil para os desenvolvedores.

As aplicações em Java são compiladas em bytecode, que pode ser executado por uma JVM. Os objetos são produzidos no heap (o espaço de memória usado para alocação dinâmica), que são, então, monitorados e rastreados por operações de coleta de lixo. A maioria dos objetos usados no código Java tem vida curta, e podem ser recuperados logo após serem criados. O coletor de lixo usa um algoritmo de marcação e varredura para marcar todos os objetos inacessíveis como coleta de lixo e, em seguida, verifica os objetos ativos para encontrar os que ainda estão acessíveis.

A coleta automática de lixo significa que você não tem controle sobre se, e quando, os objetos serão excluídos. Isso contrasta com linguagens como C e C++, onde a coleta de lixo é feita manualmente. No entanto, a coleta automática de lixo é popular por um bom motivo: o gerenciamento manual de memória é complicado, e retarda o ritmo de desenvolvimento de aplicações.

NEW RELIC INTEGRAÇÃO JAVA
Duke
Comece a monitoramento seus dados Java hoje mesmo.
Instale o início rápido do Java Instale o início rápido do Java

Como funciona a coleta de lixo em Java?

Durante o processo de coleta de lixo, o coletor verifica diferentes partes do heap, procurando objetos que não estão mais em uso. Se um objeto não tiver mais nenhuma referência a ele em outro lugar da aplicação, o coletor remove o objeto, liberando memória no heap. Este processo continua até que todos os objetos não utilizados sejam recuperados com sucesso.

Às vezes, um desenvolvedor escreve inadvertidamente um código que continua a ser referenciado, mesmo que não esteja mais sendo usado. O coletor de lixo não removerá objetos que estão sendo referenciados dessa forma, causando vazamentos de memória. Após a criação de vazamentos de memória, pode ser difícil detectar a causa, por isso é importante evitar que isso aconteça, garantindo que não haja referências a objetos não utilizados.

Para garantir que os coletores de lixo funcionem com eficiência, a JVM separa o heap e, em seguida, os coletores de lixo usam um algoritmo de marcação e varredura para percorrer esses espaços, e limpar objetos não utilizados. Vamos dar uma olhada mais de perto nas diferentes gerações do heap de memória e, em seguida, examinar os fundamentos do algoritmo de marcação e varredura.

Coleta de lixo geracional

Para entender completamente como funciona a coleta de lixo em Java, é importante conhecer as diferentes gerações no heap de memória, que ajudam a tornar a coleta de lixo mais eficiente. Essas gerações são divididas da seguinte forma:

Espaço Éden em Java

O espaço Éden em Java é um conjunto de memória onde os objetos são criados. Quando o espaço Éden está cheio, o coletor de lixo remove os objetos se eles não estiverem mais em uso, ou os armazena no espaço sobrevivente, se ainda estiverem sendo usados. Este espaço é considerado parte da ''geração mais jovem'' na memória.

Espaços sobreviventes em Java

Existem dois espaços de sobrevivência na JVM: sobrevivente zero e sobrevivente um. Este espaço também faz parte da ''geração mais jovem''.

Espaço titular em Java

O espaço titular é onde os objetos de longa duração são armazenados. Os objetos são eventualmente movidos para este espaço se sobreviverem a um certo número de ciclos de coleta de lixo. Este espaço é muito maior que o espaço Éden, e o coletor de lixo o verifica com menos frequência. Este espaço é considerado como a ''velha geração'' no heap.

Então, como esses diferentes espaços tornam a coleta de lixo mais eficiente? Bem, a coleta de lixo ocorre com mais frequência no espaço Éden, porque muitos objetos novos não precisam permanecer na memória por muito tempo. No entanto, não faria sentido para o coletor de lixo continuar verificando objetos não coletados repetidamente, principalmente se um objeto precisar permanecer no heap por muito tempo. Esse é um uso ineficiente do coletor. Ao mover objetos para espaços de sobrevivência e de titularidade, o coletor de lixo sabe que há uma probabilidade maior de que os objetos lá precisarão permanecer na memória, por isso verifica essas áreas com menos frequência. Como o espaço ocupado é muito maior que o espaço Éden, ele fica cheio com menos regularidade, e o coletor de lixo não o verifica tanto. A desvantagem principal é que o espaço ocupado é mais sujeito a vazamentos de memória, já que não é verificado constantemente.

Os ciclos de coleta de lixo na geração mais jovem (espaços do Éden e dos sobreviventes) são considerados coleta de lixo menor. Os ciclos de coleta de lixo na velha geração (espaço de titularidade) são conhecidos como coleta de lixo antiga, ou coleta de lixo principal, porque leva mais tempo do que a coleta de lixo secundária. Como você pode imaginar, o ciclo secundário de coleta de lixo é um processo mais simples e rápido do que a coleta principal de lixo, o que faz sentido porque ocorre com muito mais frequência, e precisa ser eficiente.

Nas versões anteriores do Java (antes do Java 8), havia uma terceira área de memória, conhecida como geração permanente (perm gen ou PermGen), que incluía metadados de aplicação necessários para a JVM. No entanto, a geração permanente foi removida no Java 8.

Marcar e varrer

O processo de coleta de lixo em Java usa um algoritmo de marcação e varredura. Veja como isso funciona:

  • Existem duas fases neste algoritmo: marcação seguida de varredura.
  • Quando um objeto Java é criado no heap, ele possui um bit de marcação definido como 0 (falso).
  • Durante a fase de marcação, o coletor de lixo percorre ''árvores'' de objetos, começando por suas raízes. Quando um objeto é acessível a partir da raiz, o bit de marcação é definido como 1 (verdadeiro). Enquanto isso, os bits de marcação para objetos inacessíveis permanecem inalterados.
  • Durante a fase de varredura, o coletor de lixo percorre o heap, recuperando a memória de todos os itens com um bit de marcação 0 (falso).

Benefícios da coleta de lixo em Java

A coleta de lixo em Java oferece vários benefícios para o gerenciamento de memória em aplicações em Java. Abaixo estão os mais importantes:

  • Gerenciamento automático de memória:

A coleta de lixo automatiza o processo de gerenciamento de memória, reduzindo a carga dos desenvolvedores de alocar e desalocar memória manualmente.

  • Prevenção de vazamentos de memória:

A coleta de lixo ajuda a evitar vazamentos de memória, recuperando automaticamente a memória ocupada por objetos que não são mais acessíveis, ou referenciados pelo programa.

  • Produtividade aumentada:

Os desenvolvedores podem se concentrar mais na lógica e nos recursos da aplicação sem se preocupar muito com o gerenciamento de memória, resultando em maior produtividade e ciclos de desenvolvimento mais rápidos.

  • Estabilidade aprimorada da aplicação:

O gerenciamento automático de memória reduz a probabilidade de erros relacionados à memória, como falhas de segmentação, ou violações de acesso, contribuindo para a estabilidade da aplicação.

  • Adaptabilidade a cargas de trabalho variadas:

A linguagem Java fornece diferentes algoritmos de coleta de lixo para atender a diversas cargas de trabalho e requisitos de aplicações. Essa adaptabilidade permite que os desenvolvedores escolham o algoritmo mais apropriado para os seus cenários específicos.

  • Desempenho otimizado:

Algoritmos eficientes de coleta de lixo, quando ajustados corretamente, contribuem para um desempenho otimizado, minimizando os tempos de pausa, e garantindo que a aplicação gaste mais tempo executando código útil.

 

O que aciona a coleta de lixo em Java?

Existem três tipos principais de eventos que acionam a coleta de lixo no heap.

  • Eventos menores: ocorrem quando o espaço do Éden está cheio, e os objetos são movidos para um sobrevivente. Um pequeno evento acontece na área jovem.
  • Eventos mistos: São eventos menores, que recuperam objetos da velha geração.
  • Grandes eventos: abrem espaço tanto para as gerações mais jovens quanto para as mais velhas, o que leva mais tempo do que outros tipos de eventos de coleta de lixo.

Tipos e estratégias de coleta de lixo em Java

A linguagem Java inclui quatro opções diferentes de coletores de lixo, cada uma com as suas próprias vantagens e desvantagens.

Coletor de lixo em série

O coletor de lixo em série normalmente é usado para ambientes menores, e de cadeia única. Não utilize em ambiente de produção, porque o processo de coleta de lixo assume o controle da cadeia, pausando outros processos. Isto é conhecido como o evento “pare o mundo”.

Coletor de lixo paralelo

O coletor de lixo paralelo é o coletor padrão da JVM. Como o nome indica, esse coletor de lixo usa várias cadeias (paralelas). Como também pode usar várias CPUs para acelerar o taxas de transferência, também é conhecido como coletor de taxas de transferência. No entanto, ao executar a coleta de lixo, também pausará as cadeias da aplicação.

Coletor simultâneo de marcação e varredura (CMS)

Assim como o coletor de lixo paralelo, o coletor de marcação e varredura simultâneo usa várias cadeias. No entanto, esse coletor é conhecido como coletor de “pausa baixa” porque pausa cadeias de aplicações com menos frequência, tornando-o mais apropriado para aplicações voltadas para o usuário, onde eventos de “pare o mundo” causarão problemas para os seus usuários. Porém, ele só pode coletar o lixo da geração mais velha simultaneamente—ele ainda precisa pausar a execução ao coletar a geração mais jovem. Além disso, como as cadeias do coletor são executadas ao mesmo tempo que as cadeias da aplicação, ele usa mais poder de processamento do que outros coletores de lixo.

Coletor de lixo (G1) lixo primeiro

O coletor de lixo G1 adota uma abordagem totalmente diferente. Ao invés de recolher as gerações jovens e velhas separadamente, pode recolher ambas ao mesmo tempo, dividindo o heap em muitos espaços—não apenas os espaços do Éden, dos sobreviventes e dos titulares, que outros recolhedores de lixo utilizam. Isto permite limpar regiões menores, ao invés de limpar regiões grandes de uma só vez, otimizando o processo de coleta. Ele é executado simultaneamente como o coletor CMS, mas muito raramente pausa a execução, e pode coletar as gerações mais novas e mais velhas simultaneamente.

É possível forçar a coleta de lixo?

Infelizmente, você não pode forçar a coleta de lixo, mesmo que a sua JVM esteja utilizando quase 100% do heap. No entanto, existem alguns truques que você pode usar, para ajudar a garantir que os objetos em Java sejam coletados como lixo.

Garantindo que um objeto em Java seja removido durante a coleta de lixo

Você não pode forçar a coleta de lixo em um objeto específico, mas pode atualizar os objetos para que eles não fiquem mais acessíveis ao restante da aplicação. Isso permite que o coletor de lixo saiba que esses objetos devem ser removidos.

Você pode tornar objetos inacessíveis das seguintes maneiras:

  • Crie um objeto dentro de um método.Depois que os métodos são executados, todos os objetos chamados nesses métodos tornam-se inacessíveis, o que os torna elegíveis para a coleta de lixo.
  • Anule a variável de referência.Você pode alterar uma variável de referência para NULL. Contanto que todas as referências a um objeto sejam removidas, esse objeto se tornará inacessível, o que permite ao coletor de lixo saber que o objeto pode ser removido.
  • Reatribua a variável de referência.Em vez de anular a variável de referência, você também pode reatribuir a referência a outro objeto. Mais uma vez, desde que todas as referências a um objeto sejam removidas, seja tornando as variáveis de referência NULL, ou reatribuindo-as, o objeto se tornará inacessível, fazendo com que ele seja removido durante o processo de coleta de lixo.
  • Crie um objeto anônimo. Um objeto anônimo não tem uma referência, então o coletor de lixo irá marcá-lo e removê-lo durante o próximo ciclo de coleta de lixo.

Melhores práticas de coleta de lixo em Java

As melhores práticas de coleta de lixo em Java podem variar com base nas características específicas da sua aplicação. Recomendamos avaliar e ajustar regularmente as suas estratégias de coleta de lixo para atender às crescentes demandas de desempenho. Abaixo estão algumas estratégias para você começar:

  1. Escolha o coletor de lixo certo:

Selecione um coletor de lixo com base nos requisitos e características da sua aplicação. Diferentes coletores são otimizados para vários cenários, como baixa latência, taxas de transferência, ou tempos mínimos de pausa.

  1. Monitore e analise os logs de coleta de lixo:

Analise regularmente os logs de coleta de lixo para identificar padrões, diagnosticar problemas, e ajustar as configurações de coleta de lixo.

  1. Otimize o tamanho do heap:

Ajuste o tamanho do heap para corresponder aos requisitos de memória da aplicação. Um heap de tamanho adequado ajuda a evitar problemas, como coletas de lixo frequentes, ou erros de falta de memória.

  1. Ajuste os parâmetros de coleta de lixo:

Entenda e ajuste os parâmetros de coleta de lixo, como o tamanho das gerações mais novas e mais velhas, contagens de cadeias, e intervalos de coleta. Esse ajuste pode impactar significativamente o desempenho.

  1. Minimize a criação de objetos:

Minimize a criação desnecessária de objetos para reduzir a frequência dos ciclos de coleta de lixo. Reutilize objetos quando possível, e esteja atento ao gerenciamento do ciclo de vida dos objetos.

  1. Use paralelismo e simultaneidade:

Aproveite as opções de coleta de lixo paralela e simultânea para melhorar o desempenho. Coletores paralelos podem utilizar múltiplas cadeias, reduzindo o tempo de pausa.

Monitorando o desempenho de aplicações em Java com a New Relic

A coleta de lixo em Java pode afetar o desempenho da sua aplicação em Java, especialmente se você estiver usando um coletor de lixo que pausa cadeias. Além disso, é importante entender como funciona o processo de coleta de lixo, e garantir que o coletor de lixo saiba quando remover objetos do heap. Caso contrário, você poderá ter problemas de desempenho devido a vazamentos de memória e outros problemas. Então, como você monitora a sua aplicação em Java para otimizar o desempenho, e detectar e fazer a triagem de problemas?

2024 STATE OF THE JAVA ECOSYSTEM
Man holding a cup of coffee
Get an in-depth look at one of the most popular programming languages.
View the Report View the Report

Com a Início Rápido Da New Relic Para Java, você pode configurar o monitoramento de aplicações em Java em minutos. A início rápido conta com um dashboard com visualizações, que incluem tempo de CPU de coleta de lixo, utilização de CPU, memória física média, heap de memória usado, e memória média usada. Com essas métricas, você pode ver que a coleta de lixo está impactando o desempenho da sua aplicação, e ajustar as configurações do heap de memória e do coletor de lixo. A início rápido também inclui alertas pré-definidos para alta utilização de CPU, uso de memória, erros de transação e pontuação Apdex, e torna fácil alertar as suas equipes por meio de ferramentas, como Slack e PagerDuty, quando surgirem problemas.