Como Funciona o Garbage Collector

Super GC

O Garbage Collector (GC) é um dos pilares do funcionamento da Java Virtual Machine (JVM), automatizando a gestão de memória ao desalocar objetos que não são mais necessários. Este artigo explora o funcionamento detalhado do GC, com foco nas principais áreas de memória: Young Generation, Old Generation e Metaspace. Entenda como essas áreas impactam o desempenho da sua aplicação Java.

O Papel do Garbage Collector na JVM

Na JVM, a memória é gerenciada de forma automática pelo Garbage Collector. Esse mecanismo garante que a aplicação funcione sem vazamentos de memória, liberando espaço na heap para novos objetos. A heap é dividida em diferentes regiões, otimizando o processo de coleta de lixo e alocação de memória.

A maior parte dos coletores implementa o conceito de coleta generacional, que organiza os objetos em gerações baseadas no seu tempo de vida. Essa abordagem maximiza a eficiência da coleta, pois a maioria dos objetos em Java tem vida curta.

Estrutura da Memória Heap na JVM

Estrutura do Heap
Estrutura do Heap

A memória heap da JVM é dividida em três áreas principais: Young Generation, Old Generation e Metaspace. Cada uma desempenha um papel específico no gerenciamento da memória.

Young Generation

A Young Generation é a área onde a maioria dos objetos recém-criados são alocados. Como a maioria dos objetos em Java tem vida curta, essa região é coletada frequentemente, em um processo chamado Minor GC.

Exemplos de objetos coletado pelo Minor GC

Objeto Criado em Escopo de Método

Quando um objeto é criado dentro de um método, ele estará disponível para o GC assim que o método terminar sua execução, desde que nenhuma referência ao objeto seja mantida fora do método.

Exemplo:

public class GarbageCollectorDemo {
	public static void main(String[] args) {
		createObject();
		// Aqui o objeto não está mais acessível e pode ser coletado
		System.gc(); // Solicita que o GC seja executado (não garante execução imediata)
	}

	public static void createObject() {
		String temp = new String("Objeto temporário");
		System.out.println("Objeto criado: " + temp);
	}
}

 

Explicação:

  • O objeto temp é criado no escopo do método createObject.
  • Após a execução do método, temp não é mais acessível, tornando-o elegível para coleta.

Objeto com Referência Substituída

Quando uma variável de referência é atribuída a outro objeto, a referência anterior é perdida (se nenhuma outra variável aponta para ela).

Exemplo:

public class GarbageCollectorDemo {
	public static void main(String[] args) {
		createObject();
		// Aqui o objeto não está mais acessível e pode ser coletado
		System.gc(); // Solicita que o GC seja executado (não garante execução imediata)
	}

	public static void createObject() {
		String temp = new String("Objeto temporário");
		System.out.println("Objeto criado: " + temp);
	}
}

 

Explicação:

  • O objeto "Primeiro Objeto" fica inacessível quando ref1 aponta para "Segundo Objeto".
  • "Primeiro Objeto" torna-se elegível para coleta.

Objeto Criados em Estruturas Temporárias

Objetos criados dentro de loops ou coleções temporárias podem ser descartados assim que o contexto não os utiliza mais.

Exemplo:

import java.util.ArrayList; 

public class GarbageCollectorDemo { 
	public static void main(String[] args) { 
		for (int i = 0; i < 1000; i++) { 
			String temp = "Objeto " + i; 
			System.out.println(temp); 
		} 
		System.gc(); 
	} 
}

 

Explicação:

  • Cada objeto "Objeto i" criado dentro do loop é elegível para o GC ao final de cada iteração, porque sua referência não é armazenada.

Removendo Referências de Objetos Explicitamente

Quando um objeto dentro de uma coleção ou variável é explicitamente removido, ele pode se tornar elegível para o GC.

Exemplo:


import java.util.ArrayList;

public class GarbageCollectorDemo {
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<>();
		list.add(new String("Objeto na lista"));
		System.out.println("Lista antes: " + list);

		list.clear(); // Remove todas as referências
		System.gc();

		System.out.println("Lista após: " + list);
	}
}

Explicação:

  • O método clear() remove todos os elementos da lista, tornando-os elegíveis para coleta.

Referências Fracas (Weak References)

Objetos referenciados por WeakReference são automaticamente elegíveis para o GC quando não há outras referências fortes.

Exemplo:


import java.lang.ref.WeakReference;

public class GarbageCollectorDemo {
	public static void main(String[] args) {
		WeakReference<String>; 
		weakRef = new WeakReference<>(new String("Objeto Fraco"));

		System.out.println("Antes do GC: " + weakRef.get());
		System.gc();
		System.out.println("Após o GC: " + weakRef.get());
	}
}

Explicação:

  • WeakReference permite que o GC colete o objeto mesmo enquanto a referência fraca ainda existe.

Testando o GC com System.gc()

Embora você possa solicitar a execução do Garbage Collector com System.gc(), sua execução não é garantida. No entanto, ela é útil para demonstrações e testes.

Subdivisões da Young Generation:

1. Eden Space:

•Todos os objetos começam no Eden Space.

•A coleta é muito frequente nessa região, pois a maioria dos objetos se torna elegível para descarte logo após sua criação.

2.Survivor Spaces (S0 e S1):

•Objetos que sobrevivem a uma coleta no Eden são movidos para um dos dois Survivor Spaces.

•Após várias coletas, se o objeto ainda está ativo, ele é promovido para a Old Generation.

Por que a Young Generation é importante?

A coleta frequente no Eden Space libera memória rapidamente para novos objetos, evitando pressão no restante da heap. Essa estratégia mantém a aplicação eficiente e com baixa latência.

Old Generation

A Old Generation armazena objetos de longa duração que sobrevivem a várias coletas na Young Generation. Aqui, as coletas ocorrem com menos frequência, mas são mais demoradas, em um processo chamado Major GC.

Características da Old Generation:

•É onde estão armazenados objetos grandes ou persistentes.

•Quando essa área fica cheia, ocorre uma Major GC, que pode impactar o desempenho da aplicação devido à duração maior da coleta.

Estratégias para otimizar:

•Evite criar muitos objetos grandes desnecessários que rapidamente lotem a Old Generation.

•Monitore o comportamento da heap para identificar gargalos.

Metaspace

A Metaspace é a região da memória que substituiu a antiga PermGen (Permanent Generation) a partir do Java 8. Essa área é utilizada para armazenar metadados das classes carregadas pela JVM, como:

•Estruturas de classes.

•Métodos.

•Informações sobre métodos estáticos.

Diferenças entre Metaspace e PermGen:

Tamanho Dinâmico: Enquanto a PermGen tinha um limite fixo configurado pela JVM, o Metaspace pode crescer dinamicamente, dependendo da memória disponível no sistema.

Armazenamento na Memória Nativa: O Metaspace não utiliza a heap da JVM; ele é alocado na memória nativa, o que reduz o risco de erros como OutOfMemoryError: PermGen Space.

Boas práticas para Metaspace:

•Monitore o uso do Metaspace em aplicações com muitas classes dinâmicas (como frameworks que criam proxies, exemplo: Hibernate ou Spring).

•Configure limites com parâmetros como -XX:MetaspaceSize e -XX:MaxMetaspaceSize para evitar crescimento excessivo.

Processo de Garbage Collection

O Garbage Collector realiza seu trabalho em três etapas principais:

1.Marcação (Marking):

•Identifica quais objetos estão vivos e quais podem ser descartados. Isso é feito percorrendo as referências a partir das threads ativas da aplicação.

2.Compactação (Compacting):

•Após a remoção de objetos mortos, os objetos vivos são realocados para eliminar fragmentação e otimizar o uso da memória.

3.Alocação (Allocation):

•A memória livre compactada é disponibilizada para novos objetos.

Esse ciclo é realizado de forma contínua, garantindo que a memória seja gerenciada de maneira eficiente.

Conclusão

Compreender o funcionamento do Garbage Collector e as áreas da memória na JVM é essencial para otimizar o desempenho de aplicações Java. A Young Generation, Old Generation e Metaspace têm papéis específicos e complementares no gerenciamento de memória, e conhecer seus comportamentos ajuda a evitar gargalos e problemas de desempenho.

Para monitorar e ajustar o uso da memória, use ferramentas como Java Mission Control ou ative logs de GC com:


-Xlog:gc*

Com uma gestão cuidadosa e um entendimento aprofundado do GC, você pode garantir que sua aplicação seja eficiente, escalável e confiável.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top