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.
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.
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.
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
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).
Teste sua intuição
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.