Garbage Collection em Java (1)

Publicado em 02/01/2015

Este é o primeiro de uma série de artigos sobre Garbage Collection em Java, traduzidos dos originais publicados por Richard Warburton no seu blog. Richard é autor do livro da editora O'Reilly Java 8 Lambdas. (Tradução e publicação autorizadas pelo autor.)

Visão geral da Heap

Espero ser capaz de cobrir um pouco da teoria e analisar todos os coletores mais importantes na máquina virtual hotspot,1 ao longo desta série. Este artigo explica apenas o que é garbage collection e os elementos comuns aos diferentes coletores.

Por que devo me preocupar?

A máquina virtual Java gerencia a memória para você – o que é muito conveniente –, mas sua regulagem padrão pode não vir no ponto de melhor desempenho. Ao compreender um pouco a teoria por trás da garbage collection poderemos ajustar seu coletor mais facilmente. Uma preocupação comum é com a eficiência do coletor, ou seja, quanto tempo o programa gasta executando o código, em relação ao tempo dispendido na coleta de lixo. Outra preocupação é quanto ao tempo em que o aplicativo fica parado.

Existem muitas lendas e boatos envolvendo a garbage collection, de forma que a compreensão dos algorítimos um pouco mais detalhadamente nos ajuda a não cair nas ciladas e armadilhas costumeiras. Além disso, para qualquer um que se interesse em aprender como são aplicados e utilizados na prática os princípios da ciência da computação, é muito compensador examinar as entranhas da JVM.

O que significa “parar o mundo”?

Um programa (ou mutator usando a terminologia GC) está sempre criando objetos à medida em que é executado. Em algum ponto a heap precisa ser coletada e todos os coletores na hotspot param o aplicativo. O termo “parar o mundo” significa que todas as threads do mutator são pausadas.

É possível implementar um garbage collector que não precise efetuar pausas. A Azul Systems implementou efetivamente um coletor que não precisa de pausas na sua máquina virtual Zing. Não vou explicar aqui como ele funciona, mas há um whitepaper muito interessante, caso você queira se aprofundar no assunto.

A Hipótese Geracional Jovem Fraca

Trocando em miúdos: a maioria dos objetos alocados morrem jovens2. Este conceito foi demonstrado empiricamente analisando a alocação de memória e os padrões de sobrevida de um grande grupo de programas durante a década de 1980. O que os pesquisadores descobriram foi não apenas que a maioria dos objetos morrem jovens, mas também que, uma vez ultrapassada certa idade, tendem a ter uma sobrevida longa. O gráfico abaixo foi tirado de um estudo da SUN/Oracle observando a vida útil dos objetos na forma de um histograma.

Histograma da heap

Como é organizada a Heap?

A hipótese geracional jovem deu origem à ideia de garbage collection geracional na qual a heap é dividida em várias regiões, e a colocação dos objetos dentro de cada região corresponde à sua idade. Um elemento que é comum a todos os garbage collectors mencionados acima (todos menos o G1)3 é a maneira como a heap é organizada em diferentes espaços.

Organização da heap

Quando os objetos são inicialmente alocados, são armazenados no espaço do Eden se couberem. Caso o objeto sobreviva a uma coleta passa então para o espaço Survivor. Se ele sobreviver algumas vezes (o limiar de tenuring), passa então para o espaço Tenured (vitalício). As especificidades dos algorítimos para coletar esses espaços diferem de acordo com o coletor e, assim sendo, vou analisá-los separadamente num artigo futuro.

Esta divisão é benéfica porque permite usar diferentes algorítimos em espaços diferentes. Alguns algorítimos GC são mais eficientes se a maioria dos seus objetos estiver morta e outros são mais eficientes se a maioria deles estiver viva. Em razão da hipótese geracional, normalmente, quando chega a hora de coletar, a maioria dos objetos nos espaços Eden e Survivor estão mortos, e a maioria dos objetos em Tenured estão vivos.

Há ainda a Permgen – ou geração permanente. Esta é uma geração especial que mantém os objetos relacionados à própria linguagem Java. As informações sobre as classes carregadas, por exemplo, são mantidas ali. Historicamente, Strings que tenham sido internadas ou sejam constantes, também são mantidas lá. A geração permanente está sendo trocada pelo metaspace.

Múltiplos Coletores

A máquina virtual hotspot tem, na verdade, uma diversidade de Garbage Collectors. Cada um possui um conjunto diferente de características de desempenho e é mais (ou menos) adequado para diferentes tarefas. Os Garbage Collectors chave que vou examinar são:

  • Parallel Scavenge (PS) [Limpador Paralelo]: o coletor default nas edições recentes das JVM. Eles param o mundo para poder coletar, mas o fazem em paralelo (i.e. usando threads múltiplas).
  • Concurrent Mark Sweep (CMS) [Marcador de Varrição Concorrente]: este coletor tem diversas fases, algumas das quais param o mundo, mas também rodam concorrentemente com o programa em várias de suas fases.
  • Incremental Concurrent Mark Sweep (iCMS) [ Marcador de Varrição Concorrente Incremental]: uma variante do CMS, projetado para pausas menores. Algumas vezes ele consegue fazer isso!
  • Garbage First (G1) [Primeiro Lixeiro]: um novo coletor que recentemente se tornou mais estável e cujo uso vem aumentando aos poucos.

Conclusões

Apresentei alguns tópicos para reflexão introdutória sobre garbage collection. No próximo artigo vou analisar o coletor Parallel Scavenge – que é atualmente o coletor padrão. Gostaria também de fornecer um link para o meu empregador, que tem um GC log analyser (analisador do log dos GC), o qual acredito possa ser muito útil.


Notas

1 hotspot é o nome dado à base de código comum por trás do openjdk e da JVM oficial da Oracle. A partir de Java 7, o openjdk é a implementação de referência para a Java SE.

2 Tecnicamente o que eu descrevi acima é a “hipótese geracional fraca” cuja validação é empírica. Há também uma variante forte, que pode ser assim declarada: “o tempo médio de vida de um objeto criado na heap é igual à quantidade média do meio de armazenamento disponível”. Isso na verdade pode ser provado matematicamente usando a Lei de Little e considerando λ igual a 1. Prova muito simples!

3 Explicarei a forma como a heap é organizada dentro do G1 num artigo específico sobre o tema.

Compartilhe esta postagem