A coleta de lixo em Java é um dos tópicos avançados. O conhecimento em 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 não estão em 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 das opções do JVM, vamos explorar isso mais tarde neste tutorial.
Como funciona a Coleta Automática de Lixo?
A coleta automática de lixo é um processo de olhar para a memória 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 Generacional. Neste método, o espaço Heap é dividido em gerações como a Geração Jovem, a Geração Antiga ou Madura e a Geração Permanente. O espaço do heap da geração jovem é o novo local onde todos os novos objetos são criados. Uma vez que é preenchido, ocorre uma coleta de lixo menor (também conhecida como GC menor). Isso significa que todos os objetos mortos desta geração são destruídos. Esse processo é rápido porque, como podemos ver no gráfico, a maioria deles estaria morta. Os objetos sobreviventes na geração jovem são envelhecidos e eventualmente movidos para as gerações mais antigas. A Geração Antiga é usada para armazenar objetos de longa duração. Tipicamente, um limiar é definido para o objeto da geração jovem e quando essa idade é alcançada, o objeto é movido para a geração antiga. Eventualmente, a geração antiga precisa ser coletada. Este evento é chamado de GC maior (coleta de lixo maior). Muitas vezes é muito mais lento porque envolve todos os objetos vivos. Além disso, há o GC completo, o que significa limpar toda a Heap – tanto os espaços da geração jovem quanto os 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 realmente fornece quatro coletores de lixo diferentes, todos eles geracionais. Cada um deles tem suas próprias vantagens e desvantagens. A escolha de qual coletor de lixo usar fica por nossa conta, e pode haver diferenças dramáticas na taxa de transferência e pausas da aplicação. Todos esses coletores dividem o heap gerenciado em diferentes segmentos, usando a antiga suposição de que a maioria dos objetos no heap tem curta duração e deve ser reciclada rapidamente. Portanto, os quatro tipos de coletores de lixo são:
Coletor Serial
Este é o coletor de lixo mais simples, projetado para sistemas de thread único e tamanho pequeno de heap. Congela todas as aplicações durante o trabalho. Pode ser ativado usando a opção do JVM -XX:+UseSerialGC
.
Coletor Paralelo/Throughput
Este é o coletor padrão da JVM no JDK 8. Como o nome sugere, ele usa várias threads para percorrer o espaço de heap e realizar compactação. Uma desvantagem deste coletor é que ele pausa as threads da aplicação ao realizar GC parcial ou completo. É mais adequado para aplicações que podem lidar com essas pausas e tentam otimizar a sobrecarga da CPU causada pelo coletor.
O coletor CMS
O algoritmo do coletor 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”). Este coletor 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 da thread 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 voltar e fazer alguns toques finais para garantir que os objetos certos estejam marcados. Este coletor pode enfrentar falhas de promoção. Se alguns objetos da geração jovem precisarem ser movidos para a geração antiga e o coletor não tiver tempo suficiente para liberar espaço na geração antiga, ocorrerá uma falha na promoção. Para evitar isso, podemos fornecer mais espaço do heap para a geração antiga ou mais threads de fundo para o coletor.
Coletor G1
Por último, mas não menos importante, temos o coletor Garbage-First, 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. Existe uma fase de marcação global concorrente para determinar a vitalidade 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 objetos inalcançá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 pausa para atender a 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 conforme mostrado na figura:
-
Fase apenas para jovens: Esta fase inclui apenas os 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 está ocupada até um certo limite, ou seja, o limiar de ocupação inicial do heap. Neste momento, o G1 agenda uma coleta inicial apenas para jovens em vez de uma coleta regular apenas para jovens.
-
Marcação Inicial: Este tipo de coleção inicia o processo de marcação além de uma coleção apenas jovem regular. A marcação concorrente determina todos os objetos atualmente vivos nas regiões da geração antiga a serem mantidos para a seguinte fase de recuperação de espaço. Enquanto a marcação não estiver completamente concluída, podem ocorrer coleções apenas jovens regulares. A marcação termina com duas pausas especiais de parada do mundo: Remark e Cleanup.
-
Remark: Esta pausa finaliza a própria marcação e realiza o processamento global de referências e a descarga de classes. Entre Remark e Cleanup, o G1 calcula um resumo das informações de vivacidade de forma concorrente, que será finalizado e usado na pausa de Cleanup para atualizar as estruturas de dados internas.
-
Limpeza: Esta pausa também leva em consideração 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 apenas jovem é concluída com uma única coleta apenas jovem.
-
Fase de recuperação de espaço: Esta fase consiste em múltiplas 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 que valesse o esforço.
G1 pode ser ativado usando a bandeira –XX:+UseG1GC
. Esta estratégia reduz as chances de o heap ser esgotado antes que as threads de fundo tenham terminado de escanear objetos inalcançáveis. Além disso, compacta o heap on-the-go, o que o coletor CMS só pode fazer no modo STW. No Java 8, uma bela otimização é fornecida com o coletor G1, chamada deduplicação de strings. Como sabemos, os arrays de caracteres que representam nossas strings ocupam grande parte do nosso espaço de heap. Uma nova otimização foi feita que permite ao coletor G1 identificar strings que são duplicadas mais de uma vez em nosso heap e modificá-las para apontar para o mesmo array de char[] interno, para evitar múltiplas cópias da mesma string residindo no heap desnecessariamente. 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 da Geração Permanente foi removido desde o Java 8. Portanto, agora, a JVM HotSpot do JDK 8 utiliza a memória nativa para a representação dos metadados de classe, que é chamada de Metaspace. A maioria das alocações para os metadados de classe é feita a partir da memória nativa. Além disso, há uma nova flag MaxMetaspaceSize para limitar a quantidade de memória usada para os metadados de classe. Se não especificarmos o 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 de metadados de classe atinge o limite de MaxMetaspaceSize. A coleta de lixo do Metaspace excessiva pode ser um sintoma de vazamento de memória de classes, classloaders ou dimensionamento inadequado para nossa aplicação. Isso é tudo sobre a Coleta de Lixo em Java. Espero que você 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