Arquitetura e Gerenciamento de Memória na Máquina Virtual Java (JVM)

Divisão da Memória em Tempo de Execução

De acordo com a especificação do Java SE, a JVM divide a memória em diversas áreas funcionais, cada uma com propósitos específicos e ciclos de vida distintos.

1. Method Area (Área de Métodos)

Esta área é compartilhada por todas as threads e armazena estruturas de nível de classe, como o pool de constantes, dados de campos, códigos de métodos e construtores. No JDK 1.7 e anteriores, era conhecida como Permanent Generation (PermGen). A partir do JDK 1.8, a PermGen foi substituída pelo Metaspace, que utiliza memória nativa em vez de uma porção fixa do heap, reduzindo erros de OutOfMemoryError relacionados ao carregamento de classes.

2. Java Heap

É a maior área de memória da JVM, criada no início da aplicação e compartilhada entre todas as threads. Todos os objetos e arrays são alocados aqui. O heap é o alvo principal da coleta de lixo (Garbage Collection). É geralmente dividido em:

  • Young Generation: Onde novos objetos são criados. Subdividida em Eden e dois Survivor Spaces (S0 e S1).
  • Old Generation: Onde residem objetos que sobreviveram a múltiplos ciclos de coleta na Young Generation.

3. Java Virtual Machine Stacks

Cada thread possui sua própria stack (pilha), criada simultaneamente à thread. Ela armazena frames, que contêm variáveis locais, resultados intermediários e dados para invocação e retorno de métodos. Cada chamada de método resulta na criação de um novo frame que é empilhdao e removido após a execução.

4. Program Counter (PC) Register

Uma pequena área de memória que armazena o endereço da instrução da JVM que está sendo executada no momento pela thread atual. Cada thread possui seu próprio PC Register.

5. Native Method Stacks

Semelhante à stack da JVM, mas utilizada para a execução de métodos nativos (escritos em linguagens como C ou C++).

Análise de Estouro de Memória (OutOfMemoryError)

Heap Space Overflow

Ocorre quando a aplicação continua criando objetos sem liberar as referências, impedindo o Garbage Collector de recuperar o espaço. Eventualmente, o heap atinge seu limite máximo (-Xmx).


// Configuração sugerida: -Xms20m -Xmx20m
import java.util.LinkedList;
import java.util.List;

public class SimuladorHeap {
    static class UnidadeDados {
        byte[] payload = new byte[1024 * 1024]; // 1MB
    }

    public static void main(String[] args) {
        List<UnidadeDados> lista = new LinkedList<>();
        while (true) {
            lista.add(new UnidadeDados());
        }
    }
}

Stack Overflow

Geralemnte causado por recursão excessiva ou profundidade de chamada de método que excede o limite da pilha definido por -Xss.


// Configuração sugerida: -Xss256k
public class AnaliseStack {
    private int profundidade = 0;

    public void executarChamada() {
        profundidade++;
        executarChamada();
    }

    public static void main(String[] args) {
        AnaliseStack analise = new AnaliseStack();
        try {
            analise.executarChamada();
        } catch (StackOverflowError e) {
            System.err.println("Limite atingido em: " + analise.profundidade);
            throw e;
        }
    }
}

Algoritmos de Garbage Collection (GC)

Critérios de Sobrevivência

A JVM determina se um objeto é "lixo" através da Análise de Alcançabilidade. Partindo de GC Roots (como variáveis locais na stack ou variáveis estáticas na Method Area), o sistema mapeia todos os objetos alcançáveis. Aqueles sem um caminho até uma GC Root são marcados para coleta.

Estratégias Comuns

  • Mark-Sweep (Marcar e Varrer): Identifica objetos inúteis e os remove. Gera fragmentação de memória.
  • Copying (Cópia): Divide a memória ao meio e copia os sobreviventes para uma nova área, limpando a antiga. Eficiente para a Young Generation.
  • Mark-Compact (Marcar e Compactar): Move os objetos sobreviventes para uma extremidade da memória, eliminando a fragmentação. Comum na Old Generation.

Coletores de Lixo

Serial e Paralel Collectors

O Serial Collector utiliza uma única thread para o GC, causando pausas "Stop The World" completas. O Parallel Scavenge é focado em alto throughput, utilizando múltiplas threads para acelerar a coleta.

CMS (Concurrent Mark Sweep)

Focado em baixa latência, tenta realizar a maior parte do trabalho de marcação e varredura simultaneamente com as threads da aplicação.

G1 (Garbage First)

Divide o heap em múltiplas regiões independentes. Ele prioriza a coleta das regiões com mais lixo, permitindo prever e limitar o tempo de pausa (pausa curta e controlada). É o coletor padrão a partir do Java 9.

Ferramentas de Monitoramento e Ajuste

O ecossistema Java fornece ferramentas robustas para diagnóstico de performance:

  • jps: Lista os processos JVM ativos.
  • jstat: Monitora estatísticas de GC e uso de memória em tempo real.
  • jmap: Gera dumps de memória (heap dump) para análise posterior.
  • jstack: Captura o estado de todas as threads de um processo (thread dump).
  • VisualVM / JConsole: Interfaces gráficas para monitoramento de threads, CPU e heap.

Processo de Carregamento de Classes (ClassLoader)

O carregamento de uma classe passa por três etapas principais: Carregamento, Ligação (Verificação, Preparação e Resolução) e Inicialização.

Modelo de Delegação

A JVM utiliza um modelo de hierarquia para carregadores de classe:

  1. Bootstrap ClassLoader: Carrega as classes base do Java (RT.jar).
  2. Extension ClassLoader: Carrega classes de extensões instaladas.
  3. Application ClassLoader: Carrega as classes definidas no classpath do usuário.

Uma classe é delegada para o pai antes de ser tentada pelo carregador atual, garantindo a integridade do sistema (evitando que uma classe core como java.lang.Object seja sobrescrita).

Ordem de Execução de Inicialização

O código abaixo demonstra a ordem de execução entre blocos estáticos, blocos de instância e construtores durante o ciclo de vida de uma classe.


class Base {
    static { System.out.println("1. Estático Base"); }
    { System.out.println("3. Instância Base"); }
    public Base() { System.out.println("4. Construtor Base"); }
}

public class SubClasse extends Base {
    static { System.out.println("2. Estático Sub"); }
    { System.out.println("5. Instância Sub"); }
    public SubClasse() { System.out.println("6. Construtor Sub"); }

    public static void main(String[] args) {
        new SubClasse();
    }
}

Tags: JVM java garbage-collection memory-management ClassLoader

Publicado em 7-7 04:01