Trilha 12 · Linguagens e compiladores

Coleta de lixo (Garbage Collection)

Como linguagens gerenciam memória automaticamente — e por que GC pause existe, o que o GC generacional resolve e como detectar ciclos.

① Intuição

Memória que não é mais usada

Em C e C++, o programador é responsável por liberar memória manualmente com free(). Esquecer de liberar causa memory leak. Liberar cedo demais causa use-after-free. Linguagens com GC (Java, Python, Go, JavaScript) resolvem isso automaticamente: o runtime detecta objetos que não podem mais ser alcançados e libera a memória.

A definição de "inacessível" é precisa: um objeto é lixo se não existe nenhum caminho de referências a partir das raízes (variáveis na stack, globals) até ele. Mesmo que dois objetos apontem um para o outro (ciclo), se nenhuma raiz os alcança, ambos são lixo.

Memory leak em GC? Sim! Se você mantém uma referência a um objeto sem precisar mais dele (ex: lista global que cresce sem ser esvaziada), o GC não o coleta — ele ainda é alcançável. Profilers de memória como Chrome DevTools Heap Snapshot detectam esses "leaks lógicos".
② Visualização interativa

Simule Mark & Sweep

Clique em Marcar alcançáveis para ver quais objetos são alcançáveis pelas raízes (BFS). Depois clique em Coletar lixo para remover os não alcançados.

1
Inicial
2
① Marcar alcançáveis
3
② Coletar lixo
ROOTObject AObject BObject CROOTObject DObject EObject FZombie GZombie HRaízes: objetos vivos por definição (variáveis globais, stack)
③ Explicação técnica

O algoritmo Mark & Sweep

// Mark & Sweep — o algoritmo de GC mais básico

function markAndSweep(roots, heap) {
  // FASE 1: Marcar — BFS/DFS a partir das raízes
  const marked = new Set();
  const queue = [...roots];          // começa pelas raízes (globais, stack)

  while (queue.length) {
    const obj = queue.shift();
    if (marked.has(obj)) continue;     // evita ciclos
    marked.add(obj);
    for (const ref of obj.references) // empilha referências
      queue.push(ref);
  }

  // FASE 2: Varrer — remover objetos não marcados
  for (const obj of heap) {
    if (!marked.has(obj)) {
      heap.delete(obj);                // libera memória
    }
  }
}

// Problema: "stop the world" — programa pausa durante o GC

Tipos de algoritmos de GC

# Principais algoritmos de GC

# 1. Reference Counting (Swift, Rust Arc, CPython)
#    Cada objeto mantém um contador de referências
obj.refcount++   # ao criar referência
obj.refcount--   # ao descartar
#    Problema: não detecta ciclos (A→B→A com nada mais apontando)

# 2. Mark & Sweep (Go, Ruby antigo)
#    Pausa o programa, marca alcançáveis, varre o resto
#    Pro: detecta ciclos. Contra: "stop-the-world" pause

# 3. Generational GC (JVM G1, V8, CPython 3.12)
#    Objetos jovens morrem cedo (Weak Generational Hypothesis)
#    Minor GC: coleta objetos jovens (frequente, rápido)
#    Major GC: coleta sobreviventes (raro, mais lento)

# 4. Incremental / Concurrent GC (Go, V8, .NET)
#    GC roda em paralelo ao programa — sem pausa visível
Tri-color marking: GCs concorrentes usam 3 cores: branco (não visitado), cinza (visitado mas referências não processadas), preto (visitado e referências processadas). O invariante é: objetos pretos nunca apontam para brancos. O GC pode rodar incrementalmente sem pausar o programa, mantendo esse invariante via write barriers — código inserido pelo compilador em toda escrita de ponteiro.
④ Projeto para programar

Implemente um GC

Mini projeto: implemente Mark & Sweep em JavaScript: represente objetos como {id, refs:[]}, implemente mark(roots) com BFS e sweep(heap) que remove não marcados. Teste com um heap de 10 objetos e 3 raízes.

Projeto principal: adicione reference counting: cada objeto tem um refcount. Ao adicionar/remover referências, atualize o contador. Quando chegar a 0, libere. Depois demonstre um ciclo que reference counting não detecta mas Mark & Sweep detecta.

Desafio extra: implemente um GC geracional simples: divida o heap em Young e Old. Novos objetos vão para Young. Objetos que sobrevivem N coletas Young são promovidos para Old. Implemente Minor GC (coleta Young) e Major GC (coleta tudo).

⑤ Exercícios rápidos

Teste sua intuição

O que torna um objeto elegível para coleta de lixo?
Qual é a principal limitação do reference counting?
Por que o GC geracional é mais eficiente que um Mark & Sweep simples?
⑥ Aplicações no mundo real

Onde você encontra isso

🗑️

GC pause em produção

O maior problema com GC em sistemas de alta disponibilidade: uma Full GC no JVM pode pausar a JVM por 500ms+. Isso causa timeouts em cascata. Soluções: ZGC/Shenandoah (pauses <1ms), G1GC, ou ajuste de heap com -Xmx.

🦀

Rust sem GC

Rust elimina GC completamente usando o borrow checker — regras de compilação que garantem que cada valor tem exatamente um dono, e é liberado deterministicamente quando o dono sai de escopo. Performance de C, segurança de Java.

🌐

V8 Orinoco

O GC do V8 (Chrome/Node.js) é chamado Orinoco. Usa GC geracional com coleta paralela (múltiplas threads) e incremental (intercalada com JS). Objetivo: manter as pauses abaixo de 1ms para não travar animações a 60fps.

🍎

ARC no Swift/ObjC

Swift e ObjC usam ARC (Automatic Reference Counting) — reference counting gerenciado pelo compilador que insere retain/release automaticamente. Para ciclos, Swift tem weak var e unowned — referências que não incrementam o contador.

← Anterior: Bytecode e VMs Próxima: JIT →