Arquitetura da JVM Java
Como a JVM é estruturada — class loader, áreas de dados em tempo de execução, mecanismo de execução e interface nativa.
A Java Virtual Machine (JVM) é o programa que executa o seu programa. Você compila arquivos fonte .java para bytecode .class neutro em relação à plataforma, e a JVM é quem carrega esse bytecode, verifica-o, organiza seus objetos na memória e o executa no hardware real. "Escreva uma vez, execute em qualquer lugar" significa realmente "compile uma vez e deixe a JVM de cada plataforma fazer o restante." Este capítulo mapeia a estrutura interna da JVM — os três subsistemas que toda implementação compartilha — para que os capítulos sobre o modelo de memória e a coleta de lixo que vêm a seguir tenham uma base sólida.
Três subsistemas
Uma JVM, independentemente do fornecedor, é organizada em três subsistemas cooperantes. Todo o restante é detalhe dentro de um deles.
| Subsistema | Responsabilidade |
|---|---|
| Class loader | Encontra, carrega, vincula e inicializa arquivos .class no tempo de execução |
| Áreas de dados em tempo de execução | A memória gerenciada pela JVM: heap, pilhas, área de métodos, registradores PC |
| Mecanismo de execução | Interpreta e compila bytecode com JIT, além de executar o coletor de lixo |
O class loader traz os tipos para dentro, as áreas de dados em tempo de execução mantêm o estado, e o mecanismo de execução roda o código. Uma chamada de método toca os três: a classe é carregada, seu frame é empilhado em uma pilha, e seu bytecode é executado.
O subsistema de class loader
As classes não são todas carregadas na inicialização. A JVM carrega uma classe de forma lazy, na primeira vez em que ela é referenciada, em três fases: loading (lê os bytes), linking (verifica o bytecode, prepara campos estáticos, resolve referências) e initialization (executa inicializadores estáticos e blocos static { }).
Os loaders formam uma hierarquia com prioridade para o pai. Quando solicitado por uma classe, um loader delega ao pai antes de tentar por conta própria — assim, um tipo central como String sempre vem do loader bootstrap confiável e nunca pode ser substituído por código da aplicação.
// Walk the loader chain of any class
ClassLoader loader = MyType.class.getClassLoader();
while (loader != null) {
System.out.println(loader.getName());
loader = loader.getParent();
}
// A null result means the bootstrap loader (native, no Java object).
System.out.println(String.class.getClassLoader()); // prints: nullOs três loaders padrão, do filho ao pai, são o loader de aplicação (classpath), o loader de plataforma (módulos do JDK como java.sql) e o loader bootstrap (java.base central, implementado em código nativo, representado como null). O capítulo sobre carregamento de classes cobre esse ciclo de vida e o modelo de delegação por completo; módulos explicam como java.base e seus semelhantes são empacotados.
Áreas de dados em tempo de execução
Depois que uma classe é carregada, seu código e dados vivem em regiões que a JVM particiona para propósitos distintos:
- Heap — compartilhado por todas as threads; cada objeto e array vive aqui. É isso que o coletor de lixo gerencia.
- Pilhas JVM — uma por thread. Cada chamada de método empilha um frame contendo variáveis locais e operandos; o frame é desempilhado no retorno. Recursão profunda o excede (
StackOverflowError). - Área de métodos (Metaspace nas JVMs modernas) — metadados de classe, o pool de constantes em tempo de execução e campos estáticos. Memória não-heap.
- Registrador PC — por thread; o endereço da instrução de bytecode que está sendo executada no momento.
// Heap allocation: 'new' carves space out of the heap
byte[] buffer = new byte[1024]; // lives on the heap, GC-managed
// Stack growth: each call adds a frame to this thread's stack
static long factorial(long n) {
return n <= 1 ? 1 : n * factorial(n - 1); // each call = one more frame
}Heap versus não-heap é a divisão principal: instâncias de objetos ficam no heap; estruturas de classe e a própria maquinaria não ficam. O capítulo sobre stack vs. heap contrasta essas duas regiões em detalhes.
O mecanismo de execução
O mecanismo de execução transforma bytecode em ação. As JVMs HotSpot modernas são adaptativas: começam interpretando o bytecode (inicialização rápida), monitoram quais métodos ficam quentes, e então os entregam ao compilador Just-In-Time (JIT), que emite código de máquina nativo otimizado. Código frio permanece interpretado; código quente é compilado — você paga o custo de otimização apenas onde ele se justifica.
O mecanismo também abriga o coletor de lixo, que recupera objetos do heap que não são mais acessíveis a partir de qualquer referência ativa, e a Java Native Interface (JNI), a ponte para bibliotecas escritas em C/C++.
// A hot loop: the JIT will compile sum() to native code after enough calls
long total = 0;
for (int i = 0; i < 100_000_000; i++) {
total += i; // interpreted at first, then JIT-compiled, then much faster
}Um exemplo prático: inspecionando a JVM em execução
Este programa não configura nada — ele solicita à JVM em execução que se descreva por meio dos beans java.lang.management e da API de class-loader. Ele informa o nome da VM e do mecanismo de execução, percorre a hierarquia de loaders, reporta memória heap versus não-heap e expande a pilha com recursão.
O que extrair da execução:
- O
RuntimeMXBeannomeia o mecanismo de execução — uma VM da família HotSpot (a linhavm namemostra algo como 'OpenJDK 64-Bit Server VM') — confirmando que o mecanismo "Server" com capacidade JIT é o que está executando seu bytecode, não um simples interpretador. - A cadeia de loaders imprime algo como
<unnamed> -> app -> platform -> bootstrap(null): cada iteração do loop subiu para o pai, e a cadeia terminou em um painull— o loader bootstrap, que é código nativo sem objeto Java. A hierarquia é real e observável, não uma metáfora. String.class.getClassLoader()énull— tipos centrais dojava.basevêm desse mesmo loader bootstrap no topo da cadeia, o que é exatamente o motivo pelo qual o código da aplicação nunca pode substituir sua própriaString. A delegação com prioridade para o pai está fazendo seu trabalho.- As linhas de memória mostram heap usado em KB mas um heap máximo em MB, e uma figura de não-heap separada: instâncias de objetos vivem no heap gerenciado pelo GC, enquanto metadados de classe (Metaspace) são contabilizados como não-heap — as duas regiões são rastreadas de forma independente.
depth(1)retorna5porque cada chamada recursiva empilhou seu próprio frame na pilha JVM desta thread e o desempilhou no retorno; a pilha é por thread e estruturada em frames, o que é por que recursão descontrolada termina emStackOverflowErrorem vez de corromper o heap.