BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Uma breve introdução ao gerenciamento de memória em Java

Uma breve introdução ao gerenciamento de memória em Java

Jevgeni Kabanov, fundador e CTO da ZeroTurnaround e desenvolvedor do JRebel, fez uma apresentação sobre o gerenciamento de memória na conferência What's Next, na França. Nela, mostrou uma visão geral de como é feito o gerenciamento da memória pelo sistema operacional e na JVM, e como otimizar o uso de memória e evitar gargalos.

Neste artigo tomamos como base a palestra de Kabanov para apresentar uma breve introdução sobre o funcionamento do gerenciamento automático de memória na JVM, além de mostrar um pouco da evolução dos algoritmos de coleta de lixo.

Memória na JVM

Internamente, a JVM mantém uma área da memória dividida em duas partes: heap e "não-heap", em que são armazenados todos os objetos criados durante a execução da aplicação e metadados das classes e métodos carregados. Esta memória é dividida em gerações, conforme a imagem a seguir (fonte):

O heap é dividido em gerações para organizar os objetos, de acordo com quanto tempo os objetos possuem referências válidas. A geração Young (jovem), onde os novos objetos são alocados, é dividida em três partes: Eden, Survivor 0 e Survivor 1. Após algum tempo, os objetos podem ser alocados na geração Tenured ("madura", em tradução livre).

A geração Perm, considerada não-heap; contém os metadados que descrevem classes e métodos carregados pela JVM. Todas as gerações possuem uma parte adicional, chamada Virtual, que é alocada apenas se houver necessidade.

O coletor de lixo em ação

Durante a execução da aplicação, o coletor de lixo (garbage collector) procura os objetos que não são mais necessários, para removê-los. O coletor monitora o uso da memória para encontrar o melhor momento para executar. Quando a geração Young está cheia, é executada uma pequena coleta para liberar espaço para a alocação de novos objetos. Quando a geração Ternured ou Perm está cheia, o coletor executa uma coleta completa.

Conforme as execuções do coletor, os objetos podem mudar entre as gerações do heap. São separados os objetos com tempo de vida curto dos de vida longa, que possuem referências válidas por um longo período de tempo. A separação entre vida longa e vida curta se dá com base na quantidade de vezes que cada objeto "sobreviveu" à coleta de lixo.

Mark-Compact

Um dos mais populares algoritmos de coleta de lixo é o Mark-Compact. Em cada execução deste algoritmo, normalmente são ignorados os objetos sem referências; em seguida todos os ponteiros dos objetos com referências válidas são reorganizados no começo do heap, ou de acordo com a geração. Por fim, são remapeadas todas as referências dos objetos. Esta reorganização é feita com custo reduzido de recursos, e evita que o heap fique fragmentado. Após organizar os objetos no heap, para adicionar um novo objeto basta incrementar o ponteiro de referência. Isso facilita a adição de novas referências e mantém a geração organizada.

Mas o coletor Mark-Compact é um algoritmo que executa as operações em série; portanto com ele só acontece uma coisa de cada vez, o que cria dois problemas. Primeiro o tempo de execução da realocação é de acordo com o tamanho do heap, ou com a quantidade de objetos existentes. Segundo, durante o remapeamento dos objetos é necessário parar tudo (stop the world) para corrigir os ponteiros e não ter problemas com referências inválidas.

Para tentar melhorar o desempenho das aplicações, existem outros algoritmos de coleta de lixo, que tentam diminuir os problemas do Mark-Compact. O Parallel Mark-Compact (Parallel MC) tenta amenizar os problemas do Mark-Compact. Faz isso usando múltiplas threads em paralelo, em vários núcleos de processadores, para realocar os objetos de forma mais rápida e diminuir o tempo de pausa da aplicação.

CMS

Outro algoritmo adicionado no Java 5 (update 6), também tenta melhorar o desempenho do coletor de lixo. É o Concurrent Mark-Sweep (CMS), no qual há uma pequena pausa inicial para marcar os objetos vivos (com referências).

Concorrentemente com a execução da aplicação é continuada a marcação dos objetos vivos; como outras threads continuam alocando novos objetos, no final ocorre mais uma pausa que utiliza várias threads em paralelo para garantir que todos os objetos vivos foram marcados. Desta forma é liberada a aplicação e, concorrentemente, os objetos mortos são coletados e é feita a troca dos objetos entre as gerações. Para economizar tempo na pausa da aplicação, o CMS não reorganiza os objetos, deixando o heap fragmentado. Há portanto a necessidade de conhecer em quais locais do heap podem ser alocados novos objetos.

G1 e além

Para melhorar a coleta de lixo das aplicações, foi adicionado no Java 7 um novo algoritmo de coleta, chamado Garbage First, mais conhecido como G1. O G1 separa o heap em diversas regiões pequenas, normalmente de 1Mb ou menos. Dessa forma, a reorganização dos ponteiros pausa a aplicação apenas durante o remapeamento da pequena região, diminuindo o tempo de espera. Ainda há necessidade de pausar a aplicação, no entanto, e o G1 é um pouco mais lento que o Mark-Compact, pois precisa controlar as regiões do heap e registrar as referências dos objetos alocados em cada região.

Há ainda soluções alternativas de garbage collection, como por exemplo a da empresa Azul Systems que criou a Zing JVM, uma implementação da máquina virtual Java baseada no HotSpot. A Azul criou o C4 (de "coletor de compactação contínuo e concorrente"). O C4 não precisa de pausas para remapear as referências. Pausa apenas se a alocação de objetos for maior e mais rápida que o tempo necessário para o coletor realocar os objetos.

O C4 usa uma estratégia parecida com o G1, separando a memória em pequenas regiões, mas também utiliza o recurso de múltiplas tabelas de paginação para os mesmo endereços de memória. O algoritmo utiliza múltiplas threads e paginação para marcar, realocar e reorganizar as referências, evitando pausar a aplicação.

O que o C4 faz é marcar uma as regiões de uma tabela de paginação como "suja"; assim, caso a aplicação tente acessá-la ocorrerá uma falha. Em outra tabela de paginação que possua referências para os mesmo objetos, algumas threads ficam realocando os objetos, ao mesmo tempo que outras threads fazem o remapeamento das referências, deixando esta tabela de paginação organizada e "limpa". Quando a aplicação tentar acessar uma referência para um objeto na tabela de paginação suja, ocorre uma falha e o C4 altera o objeto para a referência que está na tabela de paginação limpa.

Jevgeni Kabanov, conclui sua apresentação destacando que o hardware, o sistema operacional e a JVM ainda precisam melhorar sua integração, para que as aplicações de diversas abrangências possam funcionar de forma mais eficiente.

Próximos passos e referências

Para conhecer mais detalhes sobre o gerenciamento de memória, recomendamos o artigo Gerenciamento de Memória na Máquina Virtual Hotspot (pdf) e o artigo Java SE 6 HotSpot Virtual Machine Garbage Collection Tuning, ambos da Sun/Oracle, onde são apresentados com mais detalhes como funciona o coletor de lixo, bem como tipos de algoritmos e suas configurações (embora este último documento seja de 2006, os conceitos e desafios permanecem os mesmos). Mais sobre o G1 e outras técnicas recentes pode ser visto neste artigo da Dr Dobb's.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT