Javafree

Introdução ao Garbage Collection

Publicado por Tutoriais Admin em 13/07/2012 - 79.791 visualizações

Garbage Collection (GC) é provavelmente a característica mais mal entendida da plataforma Java. Em geral se pensa que a GC elimina toda a responsabilidade de gerência de memória do desenvolvedor de uma aplicação. Em outros casos ocorre o contrário, isto é, o desenvolvedor realiza muito mais trabalho do que é necessário. Um conhecimento sólido do modelo GC é fundamental para se escrever aplicações robustas e de alta performance na plataforma Java.

A importância da Garbage Collection
A performance de uma aplicação está intimamente ligada ao custo da alocação e desalocação de memória. Se uma aplicação usa uma quantidade de memória tal que força o sistema operacional a usar memória virtual, esta aplicação sofrerá um impacto na performance. Quando a memória é alocada, mas não é desalocada corretamente isto ocorre com freqüência, pois apesar da JVM ser responsável pela desalocação da memória é necessário que o desenvolvedor da aplicação deixe claro o que é memória disponível para ser desalocada.

As Garantias da GC

A seguir temos o que é dito sobre gerência de memória na Java Virtual Machine Specification (JVMS):

O heap é criado quando a virtual machine é iniciada. A alocação de heap para objetos é requisitada por um sistema de gerência de alocação automático (conhecido como garbage collector); os objetos não são explicitamente desalocados nunca. A Java virtual machine não assume nenhum tipo de sistema automático de gerência de alocação, e a técnica de gerência de alocação deve ser escolhida de acordo com os requerimentos do sistema do implementador.

Apesar de parecer confuso, o fato do modelo de Garbage Collection não ser rígido é importante e útil, caso fosse rígido poderia ser impossível de implementá-lo em todas as plataformas.

Não existe nenhuma definição completa do comportamento do garbage collector, mas muito do modelo de GC é implicitamente especificada em várias seções na Java Language Specification e JVMS. Apesar de não haver garantias do processo exato seguido, todas as virtual machines compatíveis compartilham o ciclo de vida do objetos descrito a seguir.

Ciclo de Vida do Objetos

Para se discutir GC é importante primeiro examinar o ciclo de vida dos objetos. Um objeto tipicamente passa pelos estados mostrados a seguir, desde sua alocação até sua desalocação.

1. Criado (Created)
2. Em Uso (In use)
3. Invisível (Invisible)
4. Inalcançável (Unreachable)
5. Coletado (Collected)
6. Finalizado (Finalized)
7. Desalocado (Deallocated)

Criado (Created)
Quando um objeto é criado, muitas coisas acontecem:

1. Espaço é alocado para o objeto.
2. A construção do objeto é iniciada.
3. O construtor de superclasse é chamado.
4. Os iniciadores de instância e iniciadores de varáveis de instância são executados.
5. O resto do corpo do construtor é executado.

O custo exato destas operações depende da implementação da JVM, assim como da implementação da classe que está sendo construída. O que se deve ter em mente é que este custo existe. Assim que o objeto é criado, assumindo que ele é ligado a alguma variável, ele passa para o estado Em Uso.

Em Uso (In Use)
Objetos que tem ao menos uma referência strong são considerados Em Uso. No JDK 1.1.x todas as referências são strong, o Java 2 introduziu 3 outros tipos de referência: weak, soft, phantom. O exemplo que segue cria um objeto e o liga a algumas variáveis.



A próxima figura mostra a estrutura dos objetos dentro da VM exatamente antes do método makeCat retornar. Neste momento, 2 referências strong apontam para o objeto Cat.

0

Quando o método makeCat retorna o stack frame deste método e todas as variáveis temporárias declaradas por ele são removidos. Isto deixa o objeto Cat somente com uma referência da variável estática catList (indiretamente via Vector).

Invisível (Invisible)

Um objeto está no estado invisível quando não existe nenhuma referência strong acessível pelo programa, apesar de ainda poderem existir referências. Nem todos os objetos passam por este estado, e isto tem sido uma fonte de confusão para alguns desenvolvedores. A seguir é mostrada a criação de um objeto invisível.





Neste exemplo, o objeto foo sai fora do escopo ao final do bloco try. Pode parecer que a variável temporária de referência foo vai ser retirada da pilha neste ponto e o objeto associado estará inalcançável, pois ao final do bloco try não existe sintaxe definida que permita ao programa acessar o objeto novamente. Porém uma implementação eficiente da JVM não irá zerar a referência quando esta sai do escopo. O objeto referenciado continua com uma referência do tipo strong, pelo menos, até que o método run retorne. No exemplo isto não vai acontecer por um bom tempo. Como objetos invisíveis não podem ser coletados, isto é uma possível causa de memory leaks. Para possibilitar GC neste caso, temos que explicitamente anular as referências.

Inalcançável (Unreachable)
Um objeto entra no estado inalcançável quando não existe nenhuma referência do tipo strong para ele. Quando um objeto está neste estado ele é um candidato para coleta. É importante notar que o fato de um objeto ser candidato para coleta não quer dizer que ele vai ser imediatamente coletado. A JVM é livre para atrasar a coleta até que exista uma necessidade imediata da memória consumida pelo objeto.

É importante salientar que não é qualquer referência forte que mantém o objeto na memória, a referência deve vir de:

- Variáveis temporárias na pilha (de qualquer thread);
- Variáveis estáticas (de qualquer classe);
- Referências especiais de qualquer código JNI nativo.

Referências circulares não causam necessariamente memory leaks, o código a seguir cria 2 objetos e os referencia mutuamente.



A próxima figura mostra as referências para os objetos antes do método buildDog retornar. Antes do método retornar, existem referências de variáveis temporárias da pilha no método buildDog apontando para ambos Dog e Tail.

0

Abaixo podemos ver a figura que mostra a situação após o método buildDog retornar. Neste ponto Dog e Tail se tornam inalcançáveis e são candidatos para coleta (o que pode ser atrasado por um tempo indefinido).

0

Coletado (Collected)

Um objeto está no estado coletado quando o garbage collector reconheceu um objeto como inalcançável e o aprontou para processamento final antes da desalocação. Se o processo tem um método finalize, então ele é marcado para finalização, se ele não tem um método finalize então ele passa diretamente para o estado finalizado.

Se uma classe define um finalizador, então toda instância desta classe tem que chamar o finalizador antes da desalocação. Isto significa que a desalocação é atrasada pela inclusão de um finalizador.

Finalizado (Finalized)

Um objeto está no estado finalizado se ele se mantém inalcançável mesmo após o seu método finalize (se ele tiver um) tiver rodado. Um objeto finalizado está esperando por desalocação. Note que a implementação da VM controla quando o finalizer é rodado.

A única coisa que pode ser dita é que adicionando-se um finalizador, isto vai aumentar o tempo de vida de um objeto. O que faz com que se adicionar finalizadores em objetos que se pretende que tenham vida curta é uma má idéia. Quase sempre é melhor que o desenvolvedor faça a própria limpeza do que confiar num finalizador. Usar um finalizador pode fazer também com que recursos importantes não sejam recuperados por um período de tempo indeterminado.

Um caso em que um método finalize atrasou a GC foi descoberto pelo quality assurance (QA) team trabalhando no Swing. O QA team criou uma aplicação que realizava um teste de stress, simulando imput do usuário, usando uma thread para enviar eventos artificiais para a GUI. Rodando numa versão do toolkit, a aplicação apresentou um OutOfMemoryError depois de apenas alguns minutos de teste. A causa do problema foi descoberta e era o fato de que a thread que mandava os eventos estava rodando numa prioridade maior do que a thread do finalizador. O programa ficou sem memória porque 10.000 objetos gráficos estavam esperando na fila do finalizador uma chance de rodarem seus finalizadores. O problema foi resolvido certificando-se que quando o Swing terminou seu trabalho com um objeto gráfico, dispose é chamado para assegurar que recursos nativos são liberados tão logo seja possível.

Além de aumentar o tempo de vida dos objetos o método finalize pode aumentar o tamanho do objeto. Por exemplo algumas JVM, como por exemplo a implementação clássica da JVM, adicionam um campo aos objetos que tem um método finalize, para que eles possam ser colocados numa lista encadeada na fila de espera para finalização.

Desalocado (Deallocated)

O estado desalocado é o passo final na Garbage Collection. Se um objeto está ainda inalcançável após tudo que foi feito anteriormente, ele é um candidato para desalocação. Quando e como esta desalocação ocorre é por conta da JVM.

Objetos de Referência
Antes da implementação da plataforma Java 2, todas as referências eram do tipo strong. Isto significava que não havia como o desenvolvedor interagir com o garbage collector, a não ser por métodos de força bruta como System.gc.

O pacote java.lang.ref foi introduzido como parte do Java 2, a figura seguinte mostra a hierarquia de classes neste pacote.

0

Este pacote define classes de objetos de referência que permitem um limitado grau de interação com o garbage collector. Os objetos de referência são usados para manter uma referência para algum outro objeto de um modo que o coletor pode ainda reclamar aquele objeto. Como se pode esperar a adição destes objetos de referência complicou o conceito de alcance como definido no ciclo de vida de um objeto. Entender isto é importante mesmo que você não pretenda usar este pacote. Algumas classes do core usam internamente WeakReferences, portanto você pode encontrá-las quando estiver usando memory profilers para acompanhar o uso de memória.

Tipos de Objetos de Referência

Três tipos de objetos de referência são fornecidos, cada um mais fraco do que o último: soft, weak e phantom. Cada tipo corresponde a um nível diferente de alcance:

- Referências Soft são usadas para implementar caches memory-sensitive.

- Referências Weak para implementar mapeamentos que impedem que suas chaves ou valores sejam reclamados

- Referências Phantom são para agendar ações de limpeza de pré-morte, numa maneira mais flexível do que é possível com o mecanismo de finalização do Java.

Indo do mais forte para o mais fraco, os diferentes níveis de alcance refletem o ciclo de vida de um objeto:

- Um objeto é alcançável de forma strong se alguma thread pode alcançá-lo sem ser através um objeto de referência.

- Um objeto é alcançável de forma soft se ele não é alcançável de forma strong mas pode ser alcançado através de uma referência soft.

- Um objeto é alcançável de forma weak se não é alcançável nem de forma strong, nem de forma soft, mas sim através de uma referência weak. Quando esta referência weak é zerada, o objeto fica elegível para finalização.

- Um objeto é alcançável de forma phantom se não é alcançável de nenhuma forma acima, foi finalizado e alguma referência phantom se refere a ele.

- Um objeto é inalcançável, e portanto pode ser reclamado, quando não é alcançável por nenhum do métodos acima.

Exemplo de GC com WeakReference

É provável se encontrar objetos de referências especiais quando se usa ferramentas para procurar por memory leaks. Somente referências do tipo strong vão interferir diretamente com a GC. Se forem encontrados objetos ligados por referências do tipo weak, pode-se ignorá-los do ponto de vista da GC.

A figura seguinte mostra os objetos na memória para um programa exemplo. Vamos assumir que o problema com o programa é que os objetos de Dog não estão sendo coletados, produzindo um memory leak.

0

Existem duas referências apontando para o objeto Dog, porém apenas uma é interessante do ponto de vista da GC. A WeakReference do dogCache não é importante. A referência interessante é a do Tail, que está ligada à uma thread na pilha. Para libertar Dog e o Tail associado é necessário terminar a thread que está ligada ao Tail. Quando esta thread se for tudo ficará no seu devido lugar. Quando um objeto que é apontado por uma WeakReference é coletado, a WeakReference é automaticamente colocada em null. A próxima figura mostra o resultado ao se terminar a thread.

0

Quando a thread morre, sua stack é removida. Agora a única referência strong para Dog é via Tail, e esta se torna uma simples referência circular que não pode ser alcançada pela GC. O Dog e também o Tail agora só são alcançáveis de modo weak pelo dogCache. Quando o coletor descobre isso (o que é feito de acordo com seu próprio agendamento) ele coloca a referência do tipo weak em null. Fazendo que o Dog e o Tail fiquem totalmente inalcançáveis, tornando-os então candidatos para coleta.

Referências

Arnold, Ken, and James Gosling. The Java Programming Language, Second Edition, Addison-Wesley, Reading, MA, 1998.

Gosling, James, Bill Joy, and Guy Steele. The Java Language Specification, Second Edition, Addison-Wesley, Reading, MA, 2000.

Jones, Richard, and Rafael Lins. Garbage Collection: Algorithms for Automatic Dynamic Memory Management, John Wiley & Sons, New York, 1996.

Lindholm, Tim and Frank Yellin. The Java Virtual Machine Specification, Second Edition, Addison-Wesley, Reading, MA, 1999.