5 Tipos De Reciclagem
Por que você precisa entender os mecanismos de reciclagem antes de escolher uma linguagem
Passamos anos discutindo performance, sintaxe e ecossistema. O que raramente olhamos com a devida atenção é como a linguagem lida com memória quando você não está mais usando ela. A questão não é se existe reciclagem — porque em praticamente qualquer linguagem moderna ela existe. A questão é qual tipo de reciclagem você está lidando e que danos colaterais isso causa no seu sistema. Eu vi muita gente migratingar de uma JVM para Go sem entender a diferença entre generational e mark-compact, e a única coisa que perceberam foi que o heap começou a crescer de forma estranha. Vou listar aqui os principais modelos que aparecem no mercado hoje, não como teoria de livro didático, mas como algo que você realmente encontra nos logs de produção às 3 da manhã.
Os 5 tipos de reciclagem
1. Reciclagem por contagem de referências (Reference Counting)
Esse é o mais simples de conceituar. Cada objeto carrega um número inteiro que indica quantos ponteiros vivos apontam para ele. Quando o contador zera, o objeto é liberado imediatamente. Python usa esse mecanismo, Rust também. O problema é que ele não lida com ciclos. Se o objeto A aponta para B e B aponta para A, ambos vão ter contagem 1 mesmo após serem desconectados do resto do programa. Eles nunca chegam a zero. O garbage collector mais simples não consegue identificar isso. O Python adiciona um módulo gc separado para lidar com ciclos, mas isso é um remendo, não uma solução elegante. No meu trabalho com sistemas de cache em Python, já vi vazar memória inteira porque dois objetos formavam um ciclo que o reference counting não detectava. A solução que funcionou foi forçar a quebra dos ciclos manualmente com del em pontos estratégicos do código. Funciona, mas é trabalho extra que você não deveria precisar fazer.
2. Mark and Sweep (Marcar e Varrer)
Aqui o garbage collector parte do pressuposto de que não sabe o que está vivo. Ele começa pelas root objects — variáveis globais, pilha de execução, registradores — e marca tudo que consegue alcançar. Depois, na fase de varredura, libera qualquer bloco de memória que não recebeu marcação. A grande vantagem em relação ao reference counting é que ciclos são resolvidos automaticamente, desde que pelo menos um objeto do ciclo seja alcançável a partir das roots. A desvantagem é que, durante a fase de varredura, o programa precisa parar. Isso é chamado de stop-the-world, e pode durar segundos em heaps grandes. Já depurei uma aplicação Java onde o G1GC entrava em um ciclo de pause que reduzia o throughput de 98% para 60%. O heap tinha crescido para cerca de 32GB sem que ninguém percebesse. A lição prática é: monitorar o uso de heap é tão importante quanto monitorar CPU e memória livre.

3. Stop-the-World (Parada Total)
Este não é exatamente um algoritmo diferente, mas sim uma característica que acompanha vários algoritmos de garbage collection. Quando o coletor precisa de acesso exclusivo a todos os threads da aplicação, ele os congela completamente enquanto executa a coleta. Linguagens como Java, Ce Ruby utilizam esse modelo em suas versões mais básicas. O tempo de pausa varia com o tamanho do heap. Em sistemas com heap de 8GB, pausas de 200 a 500ms são comuns. Para uma API web, isso é percepcionável. Para um jogo ou sistema financeiro em tempo real, pode ser fatal. Uma coisa que poucos mencionam: o problema não é apenas a duração da pausa, mas a imprevisibilidade. Dois GCs consecutivos no mesmo heap podem ter durações drasticamente diferentes dependendo de quanta memória foi alocada entre eles. Se você está construindo um sistema de baixa latência, essa variabilidade vai te atrapalhar mais do que uma pausa longa e previsível.
4. Reciclagem Generacional
A ideia central aqui é simples: a maioria dos objetos morre jovem. Objetos criados recentemente tendem a ser descartados rapidamente, enquanto objetos que sobrevivem várias coletações tendem a permanecer vivos por mais tempo. O garbage collector divide o heap em gerações e trata cada uma com frequência e estratégia diferentes. O Young Generation é coletado frequentemente com operações rápidas. O Old Generation é coletado com menos frequência e com algoritmos mais pesados. A maioria dos recolectores modernos de Java, V8 (JavaScript) e .NET segue esse padrão. O detalhe que eu vejo as pessoas perderem: a generational hypothesis não é sempre verdadeira. Em sistemas que mantêm pools de objetos de longa vida ou caches grandes, o Young Generation pode não ter o benefício esperado. Já configurei JVMs onde o Young Gen estava sendo coletado a cada 10ms, mas a maior parte dos objetos recém-criados simplesmente migrava para o Old Gen e ficava lá parado, ocupando memória sem ser coletada.
Ajustar o parâmetro de promoção entre gerações fez a aplicação responder melhor. Não é configuração trivial, e cada workload é diferente.

5. Reciclagem Concurrente e Incremental
Estes são os tipos mais recentes e mais complexos. A diferença fundamental é que o coletor opera simultaneamente com a aplicação, dividindo o trabalho em etapas menores que causam pausas mínimas. O concurrent garbage collector do Java (Shenandoah, ZGC) e o mark-compact incremental do Go são exemplos. O concurrent GC usa técnicas como barriers de escritura e snapshots-do-heap para manter a consistência enquanto a aplicação continua rodando. O resultado são pausas na casa de milissegundos ou menos, independentemente do tamanho do heap. O ZGC do Java consegue pausas inferiores a 1ms com heaps de vários terabytes. Mas existe um custo. Coletors concorrentes consomem mais CPU do que os stop-the-world, porque precisam fazer trabalho adicional para rastrear mudanças no heap durante a execução. Em cargas de trabalho com muita alocação concorrente, essa sobrecarga pode ser significativa. Eu vi um serviço de processamento de dados onde a migração para ZGC reduziu as pausas de 400ms para 2ms, mas aumentou o uso de CPU em 15%. O trade-off foi aceitável, mas só porque a aplicação tinha recursos ociosos para gastar.
O incremental vai além, dividindo o trabalho do coletor em pequenas fatias executadas entre operações da aplicação. É mais lento no total, mas evita pausas longas. O Ruby usa esse abordagem em suas versões mais recentes.
O que ninguém te conta sobre escolher um tipo de reciclagem
Nenhuma implementação de garbage collection é gratuita. Mesmo quando parece que nada está acontecendo, o coletor está consumindo ciclos de CPU, memória e, em muitos casos, impondo restrições de uso de memória que você não percebe até que seu sistema comece a falhar. O maior erro que vejo desenvolvedores cometerem é assumir que a reciclagem vai resolver todos os problemas de memória. Ela não resolve vazamentos lógicos, onde objetos são mantidos vivos indefinidamente por referências não intencionais. Nenhum coletor remove algo que ainda está acessível. Outro erro é confiar cegamente no coletor padrão da linguagem. O default muitas vezes é configurado para funcionar bem em Workloads genéricos, mas raramente é o ideal para o seu caso específico. Profilear o comportamento do GC com ferramentas como jcmd no Java ou pprof no Go, antes de tomar decisões de arquitetura, economiza horas de debugging.

Galeria
-
Lixos De Reciclagem _ Reciclagem Gratuita de Lixo Eletrônico – BDIV -
Classificação e reciclagem de lixo por material e tipo na lata de lixo ...