A coleta de lixo em Java é um dos tópicos avançados. O conhecimento de GC em Java nos ajuda a ajustar o desempenho em tempo de execução de nossa aplicação.
Coleta de Lixo em Java
- No Java, os programadores não precisam se preocupar em destruir os objetos que estão fora de uso. O Coletor de Lixo cuida disso.
- O Coletor de Lixo é uma thread daemon que continua em execução em segundo plano. Basicamente, ele libera a memória heap destruindo os objetos inacessíveis.
- Objetos inacessíveis são aqueles que não são mais referenciados por nenhuma parte do programa.
- Podemos escolher o coletor de lixo para nosso programa Java por meio de opções do JVM, veremos isso na próxima seção deste tutorial.
Como funciona a coleta automática de lixo?
A coleta automática de lixo é um processo de examinar a memória de heap, identificando (também conhecido como “marcação”) os objetos inacessíveis e destruindo-os com compactação. Um problema com essa abordagem é que, à medida que o número de objetos aumenta, o tempo de coleta de lixo continua aumentando, pois precisa percorrer toda a lista de objetos, procurando o objeto inacessível. No entanto, a análise empírica de aplicações mostra que a maioria dos objetos tem curta duração. Esse comportamento foi usado para melhorar o desempenho do JVM, e a metodologia adotada é comumente chamada de Coleta de Lixo Geracional. Neste método, o espaço de heap é dividido em gerações como Geração Jovem, Geração Antiga ou Madura e Geração Permanente. O espaço de heap da geração jovem é o novo onde todos os novos objetos são criados. Uma vez preenchido, ocorre a coleta de lixo menor (também conhecida como GC menor). O que significa que todos os objetos mortos desta geração são destruídos. Este processo é rápido porque, como podemos ver no gráfico, a maioria deles estaria morta. Os objetos sobreviventes na geração jovem envelhecem e eventualmente são movidos para as gerações mais antigas. A Geração Antiga é usada para armazenar objetos de longa duração. Tipicamente, é definido um limiar para o objeto da geração jovem e, quando essa idade é atingida, o objeto é movido para a geração antiga. Eventualmente, a geração antiga precisa ser coletada. Este evento é chamado de GC Major (coleta de lixo principal). Muitas vezes, é muito mais lento porque envolve todos os objetos vivos. Além disso, há o GC completo, o que significa limpar todo o Heap – tanto os espaços de geração jovem quanto mais antigos. Por fim, até o Java 7, havia uma Geração Permanente (ou Perm Gen), que continha metadados necessários pelo JVM para descrever as classes e métodos usados na aplicação. Foi removido no Java 8.
Coletor de Lixo do Java
O JVM na verdade fornece quatro coletores de lixo diferentes, todos eles geracionais. Cada um tem suas próprias vantagens e desvantagens. A escolha de qual coletor de lixo usar está em nossas mãos e pode haver diferenças dramáticas no throughput e pausas da aplicação. Todos esses coletores dividem o heap gerenciado em diferentes segmentos, usando as suposições antigas de que a maioria dos objetos no heap tem curta duração e devem ser reciclados rapidamente. Então, os quatro tipos de coletores de lixo são:
GC Serial
Este é o coletor de lixo mais simples, projetado para sistemas de thread único e tamanho de heap pequeno. Ele congela todas as aplicações enquanto trabalha. Pode ser ativado usando a opção de JVM -XX:+UseSerialGC
.
GC Paralelo/Throughput
Esta é a coletora padrão da JVM no JDK 8. Como o nome sugere, ela utiliza várias threads para percorrer o espaço de heap e realizar a compactação. Uma desvantagem desta coletora é que ela pausa as threads da aplicação durante a realização do GC parcial ou completo. É mais adequada para aplicações que podem lidar com tais pausas e tentam otimizar a sobrecarga de CPU causada pela coletora.
A coletora CMS
A coletora CMS (“concurrent-mark-sweep”) utiliza várias threads (“concurrent”) para percorrer o heap (“mark”) em busca de objetos não utilizados que podem ser reciclados (“sweep”). Esta coletora entra no modo Stop-The-World(STW) em dois casos: – Durante a marcação inicial das raízes, ou seja, objetos na geração antiga que são alcançáveis a partir dos pontos de entrada das threads ou variáveis estáticas. – Quando a aplicação alterou o estado do heap enquanto o algoritmo estava sendo executado de forma concorrente, forçando-o a retroceder e fazer alguns ajustes finais para garantir que os objetos certos estejam marcados. Esta coletora pode enfrentar falhas de promoção. Se alguns objetos da geração jovem precisarem ser movidos para a geração antiga, e a coletora não tiver tempo suficiente para liberar espaço na geração antiga, ocorrerá uma falha de promoção. Para evitar isso, podemos fornecer mais espaço de heap para a geração antiga ou disponibilizar mais threads em segundo plano para a coletora.
Coletor G1
Por último, mas não menos importante, temos o coletor Garbage-First (G1), projetado para tamanhos de heap superiores a 4GB. Ele divide o tamanho do heap em regiões que variam de 1MB a 32MB, com base no tamanho do heap. Há uma fase de marcação global concorrente para determinar a vivacidade dos objetos em todo o heap. Após a conclusão da fase de marcação, o G1 sabe quais regiões estão principalmente vazias. Ele coleta primeiro os objetos inacessíveis dessas regiões, o que geralmente resulta em uma grande quantidade de espaço livre. Assim, o G1 coleta essas regiões (contendo lixo) primeiro, daí o nome Garbage-First. O G1 também utiliza um modelo de previsão de pausas para atingir uma meta de tempo de pausa definida pelo usuário. Ele seleciona o número de regiões a serem coletadas com base na meta de tempo de pausa especificada. O ciclo de coleta de lixo do G1 inclui as fases mostradas na figura:
-
Fase apenas para jovens: Esta fase inclui apenas objetos da geração jovem e os promove para a geração antiga. A transição entre a fase apenas para jovens e a fase de recuperação de espaço começa quando a geração antiga atinge um determinado limite, ou seja, o limite de ocupação inicial do heap. Neste momento, o G1 agenda uma coleta inicial marcando apenas para jovens em vez de uma coleta regular apenas para jovens.
-
Marcação Inicial: Este tipo de coleta inicia o processo de marcação além de uma coleta regular exclusiva para jovens. A marcação simultânea determina todos os objetos atualmente vivos nas regiões da geração antiga a serem mantidos para a próxima fase de recuperação de espaço. Enquanto a marcação não estiver completamente concluída, podem ocorrer coletas regulares exclusivas para jovens. A marcação é finalizada com duas pausas especiais stop-the-world: Remark e Cleanup.
-
Remark: Esta pausa finaliza a marcação em si e realiza o processamento global de referências e descarregamento de classes. Entre Remark e Cleanup, o G1 calcula um resumo das informações de vivacidade de forma simultânea, que será finalizado e usado na pausa Cleanup para atualizar as estruturas de dados internas.
-
Cleanup: Esta pausa também considera as regiões completamente vazias e determina se uma fase de recuperação de espaço realmente seguirá. Se uma fase de recuperação de espaço seguir, a fase somente para jovens é concluída com uma única coleta exclusiva para jovens.
-
Fase de recuperação de espaço: Esta fase consiste em várias coletas mistas – além das regiões da geração jovem, também evacua objetos vivos das regiões da geração antiga. A fase de recuperação de espaço termina quando o G1 determina que evacuar mais regiões da geração antiga não renderia espaço livre suficiente para valer o esforço.
G1 pode ser habilitado usando a bandeira –XX:+UseG1GC
. Essa estratégia reduziu as chances de esgotamento do heap antes que as threads em segundo plano tenham terminado a verificação de objetos inacessíveis. Além disso, compacta o heap durante a execução, algo que o coletor CMS só pode fazer no modo STW. No Java 8, uma bela otimização é fornecida com o coletor G1, chamada duplicação de strings. Como sabemos, os arrays de caracteres que representam nossas strings ocupam grande parte do espaço no heap. Uma nova otimização foi feita que permite ao coletor G1 identificar strings duplicadas mais de uma vez em todo o heap e modificá-las para apontar para o mesmo array de caracteres interno, evitando cópias desnecessárias da mesma string no heap. Podemos usar o argumento JVM -XX:+UseStringDeduplication
para habilitar essa otimização. G1 é o coletor de lixo padrão no JDK 9.
Java 8 PermGen e Metaspace
Como mencionado anteriormente, o espaço de Geração Permanente foi removido desde o Java 8. Agora, o JVM HotSpot do JDK 8 utiliza a memória nativa para a representação dos metadados da classe, o que é chamado de Metaspace. A maioria das alocações para os metadados da classe são feitas na memória nativa. Além disso, existe uma nova flag chamada MaxMetaspaceSize para limitar a quantidade de memória usada para os metadados da classe. Se não especificarmos um valor para isso, o Metaspace redimensiona em tempo de execução conforme a demanda da aplicação em execução. A coleta de lixo do Metaspace é acionada quando o uso dos metadados da classe atinge o limite de MaxMetaspaceSize. A coleta excessiva de lixo no Metaspace pode ser um sintoma de vazamento de memória em classes, carregadores de classes ou dimensionamento inadequado para nossa aplicação. Isso conclui a discussão sobre a coleta de lixo em Java. Espero que tenha compreendido os diferentes coletores de lixo que temos em Java. Referências: Documentação da Oracle, G1 GC.
Source:
https://www.digitalocean.com/community/tutorials/garbage-collection-in-java