JVM: A Máquina Virtual Java - Fundamentos e Funcionamento
Source: Dev.to
Visão geral da JVM
A JVM executa programas Java compilados em bytecode, funcionando como uma camada de abstração entre o código Java e o sistema operacional/hardware. Ela não é apenas um interpretador; é uma especificação completa que define a execução do bytecode, o gerenciamento de memória, o funcionamento de threads e outros aspectos críticos.
Arquitetura da JVM
A arquitetura pode ser dividida em três componentes principais:
Class Loader
- Responsabilidades: Carregar arquivos
.classna memória da JVM. - Fases:
- Carregamento: Localiza e lê o arquivo
.class, criando sua representação binária. - Linkagem: Verifica a correção do bytecode, prepara estruturas para campos estáticos e resolve referências simbólicas.
- Inicialização: Executa construtores estáticos e inicializa variáveis estáticas.
- Carregamento: Localiza e lê o arquivo
- Hierarquia:
- Bootstrap ClassLoader: Carrega classes do núcleo do Java.
- Extension ClassLoader: Carrega extensões da plataforma.
- Application ClassLoader: Carrega classes da aplicação.
Essa hierarquia garante isolamento e segurança ao carregar classes.
Áreas de memória
| Área | Descrição |
|---|---|
| Heap | Aloca todos os objetos. Compartilhada entre threads. Gerenciada pelo Garbage Collector (GC). Dividida em gerações (Young, Old). |
| Method Area | Armazena metadados de classes, constantes de tempo de execução, código de métodos e construtores. Compartilhada entre threads. |
| Stack | Uma pilha por thread, contendo frames de método (variáveis locais, operandos, informações de execução). Operação LIFO. |
| Program Counter (PC) Register | Mantém o endereço da instrução JVM atual. Cada thread tem seu próprio PC. |
| Native Method Stack | Armazena informações de métodos nativos (C/C++) acessados via Java Native Interface (JNI). |
| Metaspace (a partir do Java 8) | Guarda metadados de classes; não faz parte da heap gerenciada. |
Execution Engine
- Interpretador: Lê e executa instruções de bytecode uma a uma. Simples, porém mais lento.
- JIT Compiler (Just‑In‑Time): Compila bytecode “quente” (hot spots) em código de máquina nativo, armazenando‑o em cache para execuções subsequentes. Usa otimizações como inlining, eliminação de código morto e otimizações específicas de processador.
- Garbage Collector: Gerencia automaticamente a memória, removendo objetos não mais referenciados.
Fluxo de execução de um programa Java
- Compilação: O código‑fonte
.javaé compilado pelojavacem bytecode (.class), independente de plataforma. - Inicialização da JVM: O Class Loader carrega as classes necessárias, começando pela classe que contém o método
main. O carregamento é lazy (sob demanda). - Interpretação e compilação JIT: O bytecode é inicialmente interpretado; o JIT monitora a frequência de execução e compila hot spots em código nativo, armazenando‑os em cache.
- Coleta de lixo: O GC monitora a heap, identifica objetos inacessíveis e libera sua memória automaticamente.
Garbage Collection (GC)
O GC identifica objetos alcançáveis a partir de GC roots (variáveis locais nas stacks, campos estáticos, referências JNI) e marca todos os objetos acessíveis. Objetos não marcados são considerados lixo e podem ser removidos.
Gerações da heap
- Young Generation: Onde novos objetos são alocados. Subdividida em Eden (criação inicial) e dois Survivor Spaces. Quando Eden enche, ocorre um Minor GC que move objetos sobreviventes para Survivor.
- Old Generation: Contém objetos que sobreviveram a vários ciclos de GC na Young Generation. Coletas aqui (Major ou Full GC) são menos frequentes, porém mais custosas.
- Metaspace: Armazena metadados de classes (substituiu a Permanent Generation a partir do Java 8) e não faz parte da heap gerenciada.
Coletores de lixo disponíveis
| Coletor | Características |
|---|---|
| Serial GC | Usa uma única thread; adequado para aplicações pequenas com heaps de poucos megabytes. |
| Parallel GC | Utiliza múltiplas threads; maximiza throughput, mas pode causar pausas perceptíveis. Padrão em muitas versões da JVM. |
| CMS (Concurrent Mark Sweep) | Executa a maior parte da coleta concorrentemente com a aplicação, reduzindo pausas. Descontinuado em versões recentes. |
| G1 GC (Garbage First) | Divide a heap em regiões e coleta prioritariamente as regiões mais “sujas”. Oferece pausas previsíveis; padrão desde o Java 9. |
| ZGC e Shenandoah | Coletores de baixa latência que mantêm pausas abaixo de 10 ms mesmo com heaps de terabytes, usando técnicas avançadas de coloração de ponteiros e compactação concorrente. |
JIT Compiler e compilação em camadas
A JVM moderna emprega tiered compilation, combinando interpretação com dois níveis de compilação JIT:
- C1 Compiler: Compila rapidamente com otimizações básicas, ideal para código executado poucas vezes ou durante a inicialização.
- C2 Compiler: Realiza otimizações agressivas, porém leva mais tempo; usado para hot spots que são executados muitas vezes.
Otimizações do JIT
- Inlining: Substitui chamadas de método pelo corpo do método, eliminando overhead e permitindo otimizações adicionais.
- Escape Analysis: Determina se objetos podem ser alocados na stack ao invés da heap, reduzindo a pressão sobre o GC.
- Loop Unrolling: Desenrola loops para diminuir o número de iterações de controle.
- Dead Code Elimination: Remove código que nunca será executado.
- Other advanced optimizations: Profile‑guided optimizations, speculative optimizations, and runtime de‑optimization when assumptions change.
Esta visão geral cobre os principais componentes e mecanismos da JVM, oferecendo uma base sólida para entender como o bytecode Java é executado, otimizado e gerenciado em tempo de execução.