O Funcionamento do System.gc()
Por padrão, as chamadas aos métodos System.gc() ou Runtime.getRuntime().gc() sugerem ao Java a execução de um Full GC. Esse processo tenta liberar memória tanto na geração jovem (Young Generation) quanto na antiga (Old Generation), processando objetos que não possuem mais referências ativas.
É crucial entender que o System.gc() funciona como uma sugestão, e não um comando imperativo. A JVM decide o momento exato da coleta baseada em sua própria lógica interna. Em ambientes de produção, a coleta de lixo deve ser deixada para os mecanismos automáticos do sistema. O uso manual é geralmente restrito a testes de performance ou benchmarks específicos.
// Sugere a execução do processo de finalização para objetos pendentes
Runtime.getRuntime().runFinalization();
Diferença entre Memory Overflow (OOM) e Memory Leak
Esgotamento de Memória (OutOfMemoryError)
O erro OutOfMemoryError ocorre quando o coletor de lixo (GC) não consegue liberar espaço suficiente para novas alocações, mesmo após realizar coletas intensivas. Isso geralmente acontece por dois motivos principais:
- Configuração inadequada da Heap: O tamanho definido via parâmetros como
-Xmsou-Xmxé insuficiente para o volume de dados que a aplicação processa. - Alocação de objetos massivos: Criação súbita de grandes arrays ou estruturas que excedem o espaço disponível e não podem ser descartadas pois ainda estão em uso.
Em versões antigas do JDK, era comum ver falhas no PermGen. Com a introdução do Metaspace, a gestão de metadados de classes tornou-se mais flexível, embora ainda possa ocorrer OOM se o limite de memória nativa for atingido.
Vazamento de Memória (Memory Leak)
Um vazamento ocorre quando objetos não são mais necessários pela lógica do negócio, mas permanecem referenciados, impedindo que o GC os remova. Embora não causem queda imediata, o acúmulo gradual consome a memória virtual até levar ao colapso da aplicação.
Exemplos comuns incluem:
- Padrão Singleton: Se um singleton mantém referências a objetos externos, esses objetos viverão tanto quanto a própria aplicação.
- Recursos não fechados: Conexões de banco de dados (
Connection), sockets e fluxos de I/O que não chamam o métodoclose().
O Fenômeno Stop-The-World (STW)
O termo Stop-The-World refere-se à pausa completa das threads de execução do usuário durante um evento de Garbage Collection. Durante o STW, a aplicação parece "congelar" momentaneamente.
Essa pausa é necessária para garantir a consistência do snapshot de memória durante a análise de acessibilidade (Reachability Analysis). Se as referências mudassem enquanto o coletor as mapeia, o resultado seria impreciso. Praticamente todos os coletores, incluindo o G1 e o ZGC, possuem fases de STW, embora coletores modernos busquem minimizar drasticamente esse tempo.
Paralelismo vs. Concorrência no GC
No contexto da coleta de lixo, esses termos possuem definições específicas:
- Paralelo (Parallel): Múltiplas threads de coleta trabalham simultaneamente para acelerar o processo, mas as threads da aplicação permanecem suspensas.
- Concorrrente (Concurrent): As threads de coleta executam ao mesmo tempo em que as threads da aplicação, dividindo os recursos da CPU para evitar pausas longas.
Sfaepoints e Safe Regions
Safepoints (Pontos de Segurança)
A JVM não pode interromper uma thread em qualquer linha de código. Ela utiliza Safepoints, locais específicos (como retornos de métodos ou loops) onde o estado da thread é conhecido e seguro para uma pausa. A JVM geralmente usa um mecanismo de interrupção ativa, onde as threads verificam periodicamente um sinalizador para decidir se devem parar.
Safe Regions (Regiões de Segurança)
Quando uma thread está em estado de espera (Sleep) ou bloqueada (Blocked), ela não consegue alcançar um Safepoint. Para resolver isso, utilizam-se as Safe Regions. Nestas regiões, as referências de objetos permanecem estáticas. Se o GC iniciar enquanto uma thread está em uma Safe Region, a thread poderá continuar nela, mas deverá esperar a conclusão do GC caso tente sair da região.
Classificação de Referências no Java
Desde o JDK 1.2, o Java oferece quatro níveis de força para referências, localizadas principalmente no pacote java.lang.ref:
1. Referência Forte (Strong Reference)
É o tipo padrão, como Object obj = new Object(). O GC nunca coletará um objeto enquanto houver uma referência forte apontando para ele, preferindo lançar um OutOfMemoryError.
2. Referência Suave (Soft Reference)
Objetos mantidos por SoftReference são coletados apenas quando a memória está prestes a acabar. É ideal para a implementação de caches sensíveis à memória.
// Exemplo de criação de referência suave
Usuario user = new Usuario("Admin");
SoftReference<Usuario> softUser = new SoftReference<>(user);
user = null; // Torna o objeto elegível para coleta suave
3. Referência Fraca (Weak Reference)
Diferente da suave, a WeakReference não impede a coleta no próximo ciclo de GC. Assim que o coletor identificar que um objeto possui apenas referências fracas, ele será removido, independentemente do espaço disponível na Heap.
// Exemplo de referência fraca
CacheData metadata = new CacheData("Session_01");
WeakReference<CacheData> weakMeta = new WeakReference<>(metadata);
metadata = null;
4. Referência Fantasma (PhantomReference)
A referência mais fraca de todas. Ela não permite acessar o objeto original (o método get() sempre retorna null). Seu único propósito é notificar a aplicação, através de uma ReferenceQueue, que o objeto foi removido da memória, permitindo limpezas de recursos de baixo nível.
5. Finalizer Reference
Uma referência interna usada para gerenciar a execução do método finalize(). Objetos que sobrescrevem esse método passam por um processo de coleta mais lento, exigindo pelo menos dois ciclos de GC para serem totalmente removidos.