VapVup👤
← Voltar
♻️

Garbage Collector

O Garbage Collector (GC) é um dos componentes fundamentais da JVM, responsável por gerenciar a alocação e liberação de memória automaticamente. No entanto, para desenvolvedores experientes, confiar cegamente nas configurações padrão sem entender o funcionamento dos coletores (Serial, Parallel, G1, ZGC e Shenandoah) e a mecânica das gerações do heap é um convite a pausas catastróficas e gargalos de performance sob alta carga de produção.

🗺️Analogia

GC Roots e Acessibilidade — O Mapa da Cidade

Imagine o Heap do Java como uma imensa cidade onde os objetos são residências. Em vez de registrar toda pessoa que entra ou sai de cada casa (o que seria análogo à contagem de referências), a prefeitura decide manter um mapa rodoviário partindo da praça central: as GC Roots. Se uma casa ainda possui uma estrada conectando-a ao centro da cidade, ela é considerada ativa. Se uma ponte cai e uma ilha inteira de residências fica isolada (referências circulares entre objetos órfãos), elas continuam se comunicando, mas, como não há mais nenhuma via de acesso direta vinda do centro, a prefeitura simplesmente remove a ilha inteira de uma vez só. As GC Roots são as avenidas principais que garantem a sobrevivência dos bairros.

💡Conceito

Por que o Java não utiliza Contagem de Referências?

O algoritmo de Contagem de Referências (Reference Counting), embora simples, apresenta dois grandes problemas para o ambiente multithreaded de alta performance da JVM: 1. Sobrecarga de Escrita (Write Overhead): cada atribuição de referência exige operações atômicas ou barreiras de sincronização na CPU para atualizar contadores, gerando contenção de memória entre threads. 2. Referências Circulares: se o objeto A aponta para B, e B aponta para A, os contadores de ambos nunca zeram, mesmo que o grafo completo fique inacessível pela pilha. A JVM contorna isso usando a Análise de Alcançabilidade (Reachability Analysis) via algoritmos de trace (como Mark and Sweep), iniciando a varredura a partir de raízes ativas conhecidas como GC Roots (variáveis na thread stack, referências estáticas de classes carregadas e ponteiros JNI ativos).

🔧Exemplo

GC Roots na Prática — Ciclo de Vida e Vazamento

public class MemoryLeakDemo {
    // GC ROOT: Atributos estáticos vivem no heap e duram enquanto a classe estiver carregada
    private static final List<byte[]> cachePermanente = new ArrayList<>();

    public void processarMensagem() {
        // Variável local na stack. Enquanto o método executa, ela é uma GC Root ativa.
        byte[] dadosLocais = new byte[1024 * 1024]; // 1MB no Heap

        // O array de 1MB agora é referenciado pela GC Root estática
        cachePermanente.add(dadosLocais);

        dadosLocais = null; // Anulamos a referência na stack

        // Mesmo anulada na pilha de execução do método, o array de 1MB ainda NÃO é coletado
        // pelo GC, pois continua acessível a partir da raiz estática 'cachePermanente'.
    }
}
👶Analogia

As Gerações do Heap — O Berçário e o Asilo

A Hipótese Geracional Fraca (Weak Generational Hypothesis) observa que a imensa maioria dos objetos morre jovem (ex: builders temporários ou DTOs de APIs). Para otimizar esse padrão, a JVM divide o heap em duas gerações principais: a Young Generation (um berçário dinâmico) e a Old Generation (um asilo de repouso). Os novos objetos nascem no espaço Eden. Periodicamente, o berçário sofre uma limpa rápida (Minor GC). Os sobreviventes são evacuados para uma área de transição (os Survivor Spaces). Cada sobrevivência confere um aniversário ao objeto. Ao completar a 'maioridade' (Tenuring Threshold), o objeto é promovido para a Old Generation, onde as limpezas são raras e custosas, evitando perturbar constantemente os sobreviventes de longo prazo.

💡Conceito

A Dinâmica de Coleta: Minor GC vs Major GC vs Full GC

A coleta de lixo atua em diferentes escopos do Heap: 1. Minor GC: limpa exclusivamente a Young Generation (Eden e Survivor). Usa algoritmos rápidos de cópia (evacuação), eliminando o lixo instantaneamente e reiniciando as regiões. 2. Major GC: limpa a Old Generation (geralmente disparado quando a ocupação do espaço atinge um gatilho configurado). 3. Full GC: limpa o Heap inteiro (Young, Old e Metaspace). É uma pausa Stop-The-World (STW) total que compacta a memória e reorganiza o espaço. Se a JVM entra em ciclos recorrentes de Full GC seguidos que consomem quase toda a CPU da máquina recuperando pouca memória, ela lança o 'java.lang.OutOfMemoryError: GC Overhead Limit Exceeded' para evitar travamento total da aplicação.

🔧Exemplo

Analisando Logs de GC e Identificando Promoções Prematuras

// Logs de GC unificado (JDK 9+) demonstrando uma pausa de evacuação do G1:
// [gc,start    ] GC(15) Garbage Collection (G1 Evacuation Pause) (young)
// [gc,phases   ] GC(15) Evacuate Collection Set: 18.5ms
// [gc          ] GC(15) Pause Young (Normal) (G1 Evacuation Pause) 2048M->512M(8192M) 21.3ms

// Análise técnica do log:
// - O heap utilizado caiu de 2GB (2048M) para 512MB (512M) em um heap total de 8GB (8192M).
// - A pausa levou 21.3ms. Se o valor residual após coletas sucessivas (512MB) cresce continuamente
//   rumo ao limite máximo, indica promoção de objetos ativos para a Old Gen devido a um heap
//   jovem subdimensionado ou vazamento persistente na aplicação.
🎨Analogia

G1 GC e ZGC — Divisão de Territórios

Imagine que em vez de limpar um armazém gigante varrendo o piso inteiro de uma só vez (interrompendo a circulação das pessoas), decidimos quadricular o chão em pequenos lotes independentes. O G1 funciona assim: ele monitora quais lotes possuem o maior acúmulo de sujeira e limpa prioritariamente esses lotes mais sujos (Garbage-First). Coletores mais modernos como o ZGC levam a limpeza paralela ao limite: a varredura e movimentação dos objetos acontecem concorrentemente enquanto as pessoas circulam pelo armazém. O ZGC pinta os objetos com cores diferentes (Colored Pointers) e instala catracas inteligentes (Load Barriers) nas saídas: se alguém tenta ler um objeto que acabou de ser movido pelo faxineiro, a catraca intercepta a chamada e entrega a referência nova sem travar a thread.

💡Conceito

Algoritmos Concorrentes Modernos: ZGC e Shenandoah

O ZGC e o Shenandoah são coletores concorrentes de ultra-baixa latência projetados para manter pausas STW em menos de 1ms, mesmo em heaps massivos. O ZGC utiliza Colored Pointers (codificando metadados de marcação e remap do GC nos próprios bits do endereço de referência do objeto de 64 bits) associado a barreiras de leitura (Load Barriers). Quando a aplicação acessa um objeto, a Load Barrier confere os bits da referência; se o objeto estiver sendo evacuado, a própria barreira redireciona o ponteiro (Self-Healing). O Shenandoah usa barreiras concorrentes de escrita/leitura similares para resolver o problema de compactação em tempo real, permitindo que a movimentação física dos blocos de memória ocorra com o mutator rodando simultaneamente.

🔧Exemplo

Tuning Ergonômico de GC — Menos é Mais

// Script de inicialização da JVM recomendado para servidores em nuvem rodando G1 GC:
// java -Xms6g -Xmx6g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/var/log/app-gc.log:time,tags -jar api.jar

// Recomendações de tuning com base em boas práticas modernas:
// 1. Defina -Xms e -Xmx com o mesmo tamanho para eliminar pausas causadas por redimensionamento de Heap.
// 2. Não engesse a Young Gen com flags fixas como -Xmn ou -XX:NewRatio ao rodar com G1 GC.
//    Deixar a Young Gen variável permite ao G1 ajustar dinamicamente o tamanho do Eden/Survivor
//    para respeitar de forma eficaz a meta de pausa configurada em -XX:MaxGCPauseMillis.