Artigo Java Magazine 05 - Memória e desempenho

Artigo publicado pela Java Magazine 05.

Esse artigo faz parte da revista Java Magazine edição 05. Clique aqui para ler todos os artigos desta edição

Clique aqui para ler esse artigo em PDF.

Memória e desempenho

Garbage Collection e técnicas e otimização

No artigo "Performance em Java" (Edição 3), analisamos parte da tecnologia empregada por JVMs para produzir alto desempenho, com foco nos compiladores JIT (Just-In-Time). Agora examinaremos outro componente crítico da JVM, conhecido tecnicamente como “memória de objetos”, ou popularmente como GC (Garbage Collector). A forma popular é menos precisa, pois a Garbage Collection (que também será abreviada como GC) é apenas uma parte do trabalho de organização da memória, que envolve gerenciamento do heap, alocação e limpeza.

O foco deste artigo é novamente na implementação em Java, embora os conceitos teóricos discutidos se apliquem também a outras plataformas. As dicas mais práticas são específicas para o Sun JDK 1.4.1, mas podem se aplicar a outras JVMs e a VMs em geral. E para não dizerem que eu só elogio o Java, veremos também alguns problemas e possíveis necessidades de melhoria.

Novatos em Java podem estranhar a ênfase dada ao assunto, em oposição a linguagens com “memória manual” como C++. Nessas linguagens, sabemos que há um espaço de memória conhecido como heap, que podemos alocar e desalocar com operações como new e delete. O heap é obtido sob demanda do sistema operacional e cresce automaticamente se necessário.

Programadores experientes sabem que alocação dinâmica pode ser um problema de desempenho. O heap é organizado como uma coleção de blocos livres ou ocupados; cada alocação individual exige localizar um endereço ideal no espaço livre, e cada desalocação individual pode exigir operações de desfragmentação do heap. Se desalocamos dois blocos de 4 Kb de endereços contíguos, é preciso combinar o espaço resultante num único bloco de 8 Kb, pois a fragmentação de espaço livre pode reduzir o desempenho e aumentar o heap desnecessariamente. Conscientes do problema, muitos programadores criam mecanismos para alocar memória com maior eficiência, como os allocators do C++; mas com isso estão aumentando a “complexidade acidental” e fazendo, à mão, um trabalho que deveria ser responsabilidade da linguagem/runtime.

No caso de Java e de outras linguagens de alto nível, a alocação é manual (usando new), mas a desalocação é automática, não existindo delete ou uma operação equivalente. A mudança pode parecer pequena, mas tem conseqüências profundas (se fosse fácil, ninguém faria de outro modo). A capacidade de desalocar memória automaticamente exige uma sofisticação muito superior à de gerenciadores de memória manual: o heap deve ser organizado de forma diferente e a linguagem siga determinadas regras; especialmente, a manipulação de ponteiros deve ser restrita.

Em Java, não podemos converter valores inteiros em ponteiros e vice-versa; não podemos executar operações aritméticas (como ++) sobre ponteiros; não podemos apontar para endereços arbitrários, nem ter ponteiros não-tipados (como void* do C); ponteiros para ponteiros, ou conversões entre ponteiros incompatíveis também são proibidas. Filosoficamente, não podemos assumir que o valor de um ponteiro tem qualquer relação com um endereço físico na memória.

Usamos o termo referência para ponteiros submetidos a essas regras. O processo de coleta de lixo precisa saber identificar todas as referências entre objetos do heap, de forma inequívoca, o que é possível quando o programa obedece às regras expostas para referências, mas inviável de outra forma.

É interessante observar que essas regras, ao mesmo tempo em que tornam a memória automática possível, também são fatores de segurança, impedindo que crashes sejam causados pelo uso de memória de forma indevida.

Como a GC funciona

Vamos começar examinando a estrutura de um objeto Java. Além dos atributos declarados pela sua classe, cada objeto possui um cabeçalho (header) com atributos especiais usados pela VM. Esses atributos não são diretamente acessíveis ao programador, nem são padronizados (dependem da implementação da VM), mas, de maneira geral, temos sempre um ponteiro para a classe do objeto (em Java, objeto.getClass() retorna desse ponteiro), além de uma coleção de flags usadas para várias operações especiais, como a coleta de lixo ou o controle de " [...] continue lendo...

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados