W3docs

Coleta de Lixo em Java

Como funciona a coleta de lixo em Java: alcançabilidade, raízes GC, heap geracional, mark-sweep-compact, tipos de referência e coletores.

Em Java você nunca chama free(). A JVM rastreia cada objeto que você aloca no heap e, quando um objeto não pode mais ser alcançado pelo seu programa em execução, o coletor de lixo (GC) recupera sua memória automaticamente. Você escreve código que cria objetos; o GC limpa silenciosamente atrás de você. Entender como ele decide o que é lixo — e onde no heap ele procura — é a diferença entre código que escala e código que trava sob carga.

Esta página aborda como o GC decide o que manter (alcançabilidade), como o heap é organizado para uma coleta rápida, o algoritmo mark-sweep-compact, os quatro tipos de referência, como escolher um coletor e uma demonstração executável que torna a coleta observável.

Alcançabilidade e raízes GC

O GC não procura objetos com os quais você "terminou". Ele procura objetos que ainda são alcançáveis. Partindo de um conjunto de raízes GC, ele segue cada referência. Qualquer coisa que ele possa alcançar está viva; todo o resto é lixo, independentemente de você achar que ainda precisa disso.

Raiz GCExemplo
Variáveis locaisUma referência na pilha de uma thread em execução
Campos estáticosstatic final Logger LOG = ...
Threads ativasUm objeto Thread vivo
Referências JNIObjetos mantidos por código nativo

Definir uma referência como null (ou deixá-la sair do escopo) não exclui nada — apenas remove um caminho para o objeto. O objeto só se torna coletável quando nenhum caminho a partir de qualquer raiz permanece.

Object a = new Object();   // reachable via local variable 'a'
Object b = a;              // now two references point to the same object
a = null;                  // still reachable through 'b' — not garbage
b = null;                  // now unreachable — eligible for collection

O heap geracional

A maioria dos objetos morre jovem — um escopo de requisição, uma variável temporária de loop, uma string intermediária. A JVM explora essa hipótese geracional fraca dividindo o heap em regiões e coletando a área jovem com muito mais frequência do que a antiga.

RegiãoContémColetada
Jovem (Eden + 2 espaços Survivor)Objetos recém-alocadosFrequentemente, por um rápido minor GC
Antiga (Tenured)Objetos que sobreviveram a vários minor GCsRaramente, por um major/full GC mais lento
MetaspaceMetadados de classes (não seus objetos)Quando classloaders são descarregados

Novos objetos chegam no Eden. Um minor GC copia os poucos sobreviventes para um espaço Survivor; objetos que continuam sobrevivendo são eventualmente promovidos para a geração antiga. Como minor GCs apenas varrem a pequena região jovem, eles são baratos — e é por isso que a alocação de curta duração em Java é rápida. (Para saber como pilha e heap diferem, veja Stack vs Heap; para o papel da JVM que hospeda o heap, veja Arquitetura JVM.)

Marcar, varrer, compactar

Uma coleta é executada em fases. Primeiro ela marca cada objeto alcançável percorrendo o grafo a partir das raízes. Em seguida ela varre, liberando os objetos não marcados. Muitos coletores adicionam uma fase de compactação que desliza os objetos sobreviventes juntos para que o espaço livre seja um bloco contíguo — o que mantém a alocação como um simples incremento de ponteiro e evita a fragmentação.

// Pseudocode of what the collector does for you:
// 1. mark:    visit(roots); for each reachable object, set live = true
// 2. sweep:   for each object on the heap, if !live -> reclaim its memory
// 3. compact: move survivors next to each other, update references

Você pode sugerir uma coleta com System.gc(), mas é apenas uma dica — a JVM pode ignorá-la. Nunca dependa disso para corretude; trate-o como uma ferramenta de diagnóstico, não como uma estratégia de gerenciamento de memória.

Força das referências

Nem toda referência mantém um objeto vivo igualmente. O pacote java.lang.ref permite que você diga ao GC o quanto você quer que um objeto seja mantido, o que é a base dos caches sensíveis à memória.

ReferênciaComportamento do GC
Forte (o = comum)Nunca coletada enquanto alcançável
SoftReferenceColetada somente quando a memória está baixa — boa para caches
WeakReferenceColetada no próximo GC assim que nenhuma referência forte permanece
PhantomReferenceUsada para agendar limpeza após a coleta
import java.lang.ref.WeakReference;

byte[] data = new byte[1024];
WeakReference<byte[]> ref = new WeakReference<>(data);
data = null;            // drop the only strong reference
// After the next GC, ref.get() may return null.

Vazamentos de memória ainda acontecem

Um coletor de lixo libera você de ponteiros pendentes e duplas liberações, mas não de vazamentos. Um vazamento de memória em Java é um objeto que você não usa mais, mas que ainda é alcançável a partir de uma raiz, então o GC deve mantê-lo. O heap fica cheio, o GC roda cada vez mais frequentemente e, eventualmente, você recebe OutOfMemoryError.

As causas clássicas são todas "esqueci de largar":

  • Uma coleção static (cache, lista de listeners, map) à qual você continua adicionando mas nunca remove. Campos estáticos são raízes GC, portanto tudo que eles alcançam vive para sempre.
  • Listeners ou callbacks registrados em um objeto de longa duração e nunca desregistrados.
  • Chaves deixadas em um HashMap muito depois de serem necessárias, porque o map ainda as referencia.

A solução não é uma flag — é largar as referências quando terminar (remover da coleção, desregistrar o listener) ou usar uma estrutura baseada em WeakReference como WeakHashMap para que o GC possa recuperar entradas quando nada mais aponta para a chave.

Aviso
Java não tem destrutores, e finalize() é obsoleto e não confiável — a JVM pode executá-lo tarde ou nunca. Para liberar arquivos, sockets ou outros recursos que não são memória de forma determinística, use try-with-resources e AutoCloseable, não o coletor de lixo.

Escolhendo um coletor

A JVM HotSpot vem com vários coletores com diferentes compensações entre throughput (trabalho total realizado) e latência (duração das pausas). Você escolhe um com uma flag da JVM; o padrão desde o Java 9 é o G1.

ColetorFlagMelhor para
G1 (padrão)-XX:+UseG1GCLatência/throughput equilibrados, heaps grandes
Parallel-XX:+UseParallelGCJobs em lote que priorizam throughput bruto
ZGC-XX:+UseZGCHeaps muito grandes, pausas sub-milissegundo
Serial-XX:+UseSerialGCHeaps pequenos, single-core ou contêineres
# Pick a collector and set the heap size at launch:
java -XX:+UseG1GC -Xms256m -Xmx2g MyApp

# Print what the GC is doing, with timestamps:
java -Xlog:gc* MyApp

Um exemplo prático

O programa abaixo torna o comportamento do GC observável. Ele fixa um objeto com uma referência forte, mantém outro apenas por meio de uma WeakReference, gera uma onda de lixo de curta duração, depois solicita uma coleta e reporta o que sobreviveu e como o uso do heap mudou.

java— editable, runs on the server

O que observar na execução:

  • O referente fraco imprime true antes da coleta e false depois dela, provando que uma WeakReference não mantém seu objeto vivo quando nenhuma referência forte permanece.
  • O array kept mantido fortemente imprime survived: true mesmo após System.gc(), porque é alcançável a partir de uma raiz GC e o coletor deve preservá-lo.
  • Aproximadamente 300 MB de lixo são alocados (Bytes allocated as garbage: 307200000), mas o heap usado sobe apenas para cerca de 5 MB — os minor GCs recuperam os arrays de loop de curta duração tão rápido quanto são criados.
  • Runtime.maxMemory() reporta o teto do heap (cerca de 256 MB aqui), definido por -Xmx, enquanto totalMemory() - freeMemory() é a porção usada viva que fica próxima de 3–5 MB ao longo do tempo.
  • System.gc() é apenas uma dica, mas nesta JVM ele é executado: o heap usado cai novamente e o referente fraco inacessível é limpo em vez de persistir.

Prática

Prática
Um objeto é referenciado apenas por uma variável local que acabou de sair do escopo, e por mais nada. O que o torna elegível para coleta de lixo?
Um objeto é referenciado apenas por uma variável local que acabou de sair do escopo, e por mais nada. O que o torna elegível para coleta de lixo?
Was this page helpful?