O que você vai aprender:
Como a JVM decide o que compilar e quando · A diferença real entre C1 e C2 · O que é o Code Cache e por que ele pode derrubar sua performance · GraalVM: JIT vs Native Image · Project Leyden: como o Java 25 resolve o warmup de vez
Índice
- 1. O Problema Original
- 2. Just-In-Time Compilation
- 3. Os Dois Compiladores: C1 e C2
- 4. Code Cache
- 5. GraalVM
- 6. Project Leyden
- 7. Quando Parar de se Preocupar com Warmup
- 8. Linha do Tempo e Resumo Executivo
- 9. GraalVM vs sem GraalVM — análise com dados reais
- Referências Bibliográficas
Seção 1 — O Problema Original
Por que o Java era lento e, como ele resolveu isso
A cena que todo dev Java já viveu
💡 Você mede a performance de um método logo que a aplicação sobe: 200ms.
Mede de novo 30 segundos depois: 8ms.
O que aconteceu? A aplicação estava "esquentando".
Isso tem nome, tem mecanismo e, a partir do Java 25, tem solução definitiva.
Java compila código-fonte (.java) para bytecode (.class) — um formato intermediário, agnóstico de plataforma. A JVM interpreta esse bytecode em qualquer máquina. Isso é o famoso write once, run anywhere.
O custo: interpretação instrução por instrução é lenta. Em 1996, Java rodava 10 a 20× mais lento que C.
Comparativo das abordagens
| Abordagem | Portabilidade | Performance | Exemplo |
|---|---|---|---|
| Compilado nativo | ❌ Binário por SO | ⭐⭐⭐⭐⭐ Máxima | C / C++ |
| Interpretado puro | ✅ Bytecode universal | ⭐ Baixa | Java 1.0 (1996) |
| JIT — híbrido | ✅ Bytecode universal | ⭐⭐⭐⭐⭐ Alta | Java 1.3+ (2000) |
| AOT Cache (Leyden) | ✅ Bytecode universal | ⭐⭐⭐⭐⭐ Alta + startup rápido | Java 24+ (2024) |
💬 A pergunta que originou o JIT: e se a JVM pudesse aprender enquanto roda quais partes merecem ser compiladas para código nativo — sem abrir mão da portabilidade?
Seção 2 — Just-In-Time Compilation
Como a JVM observa, decide e compila em tempo real
O JIT é o mecanismo pelo qual a HotSpot JVM observa o código em execução e, quando identifica trechos chamados com frequência (hot spots), os compila para código de máquina nativo — em uma thread separada, sem parar a aplicação.
O processo completo de um método na JVM
Passo 1 — Execução interpretada (Tier 0)
A JVM começa interpretando o bytecode linha a linha. Lento, mas funciona imediatamente. Ao mesmo tempo, dois contadores são incrementados por método:
- Method invocation count — quantas vezes o método foi chamado
- Loop back-edge count — quantas vezes os loops do método executaram
Passo 2 — Compilação pelo C1, rápida e com profiling (Tiers 1–3)
Quando os contadores cruzam o Tier3CompileThreshold (padrão: 2.000 invocações), o método entra na fila JIT. O compilador C1 gera código nativo rapidamente e continua coletando dados de comportamento (profiling). A aplicação já roda mais rápido, mas ainda não no máximo.
Passo 3 — Compilação pelo C2, otimização agressiva (Tier 4)
Quando o método atinge o Tier4CompileThreshold (padrão: 15.000 invocações), o C2 entra em ação. Ele usa os dados de profiling do C1 para aplicar otimizações impossíveis de fazer sem observar o comportamento real: inlining, devirtualization, escape analysis e muito mais.
Passo 4 — Armazenamento no Code Cache
O código nativo compilado pelo C2 vai para o Code Cache — uma região especial da memória fora do heap. A partir daí, o método roda na velocidade máxima possível, sem interpretação, sem compilação, sem overhead.
Fluxo visual
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ ┌──────────────┐
│ TIER 0 │ ──► │ TIERS 1 – 3 │ ──► │ TIER 4 │ ──► │ CODE CACHE │
│ │ │ │ │ │ │ │
│ Interpretado│ │ C1 compila │ │ C2 otimiza │ │ Nativo em │
│ puro │ │ + coleta │ │ agressivamente │ │ memória │
│ │ │ profiling │ │ │ │ especial │
└─────────────┘ └──────────────────┘ └─────────────────┘ └──────────────┘
Lento Rápido Máximo Ultra-rápido
O símbolo
%no output de-XX:+PrintCompilationindica que o método está no Code Cache — o estado mais otimizado possível.
Por que a compilação não trava a aplicação?
✅ A fila JIT e os compiladores rodam em threads separadas. Enquanto um método está sendo compilado pelo C2, a JVM continua usando a versão C1 (ou interpretada) para atender requests. Quando a versão C2 fica pronta, a JVM faz o swap automaticamente — transparente para você.
⚠️ Deoptimização — o que o curso não menciona
❌ O C2 faz otimizações especulativas baseadas no que observou até agora. Se o comportamento mudar (ex: um método que era sempre chamado com
Integeragora recebeString), a JVM pode deoptimizar o método — jogar fora a versão C2 e voltar para o C1 ou interpretado. Esse ciclo pode se repetir. Em produção, deoptimizações frequentes aparecem nos logs JIT comomade not entrante são sinais de código com comportamento inconsistente.
Seção 3 — Os Dois Compiladores: C1 e C2
Filosofias opostas, usadas na ordem certa
| C1 — Client Compiler | C2 — Server Compiler | |
|---|---|---|
| Tiers | 1, 2, 3 | 4 exclusivamente |
| Filosofia | Compila RÁPIDO, otimiza POUCO | Compila DEVAGAR, otimiza MUITO |
| Tempo de startup | Excelente | Ruim isolado |
| Peak performance | Boa | Máxima |
| Coleta profiling | Sim (tier 3) | Usa o que o C1 coletou |
| Escrito em | C++ | C++ (HotSpot) / Java (Graal) |
| Caso ideal | Apps curta duração, CLIs | APIs, servidores, microsserviços |
💬 Analogia: C1 é o mecânico que faz uma regulagem rápida para você sair logo. C2 é o engenheiro de F1 que passa a noite otimizando cada componente. A Tiered Compilation usa os dois na ordem certa, sem que você precise decidir.
Otimizações que o C2 aplica e o C1 não consegue
1. Method Inlining
// Antes — chamada separada com overhead
result = calculate(x);
int calculate(int x) {
return x * 2 + 1;
}
// Depois — C2 inlina o corpo diretamente
result = x * 2 + 1; // chamada eliminada
Elimina o overhead de chamada de método em hot paths. É a otimização mais impactante do C2.
2. Escape Analysis
// Antes — parece precisar de alocação no heap
Point p = new Point(x, y);
return p.x + p.y;
// Depois — C2 detecta que p não escapa do método
return x + y; // objeto eliminado, zero alocação
Se o objeto não sai do método, C2 pode alocar na stack ou eliminar completamente. Reduz pressão no GC.
3. Devirtualization
// Antes — polimórfico: qual método chamar?
animal.sound(); // Dog? Cat? Bird?
// Depois — C2 observou: 99% das vezes é Dog
dog.sound(); // chamada direta + inlining
Se 99% das chamadas são para o mesmo tipo concreto, C2 inlina esse tipo e adiciona um guard raro.
4. Loop Unrolling
// Antes — loop com overhead de controle
for (int i = 0; i < 4; i++)
sum += arr[i];
// Depois — C2 desenrola o loop
sum += arr[0];
sum += arr[1];
sum += arr[2];
sum += arr[3];
Reduz iterações, habilita vetorização SIMD e elimina o custo do contador de loop.
5. Dead Code Elimination
// Antes
boolean DEBUG = false;
if (DEBUG) {
log.trace("calculando..."); // nunca vai rodar
}
return x * 2;
// Depois — C2 remove o bloco morto completamente
return x * 2;
6. Range Check Elimination
// Antes — Java verifica bounds a cada acesso
for (int i = 0; i < arr.length; i++)
sum += arr[i]; // arr[i] verifica bounds toda vez
// Depois — C2 prova que o índice é sempre válido
// verificação eliminada no loop quente
Seção 4 — Code Cache
A região de memória que faz tudo isso funcionar e, que pode te trair
Após compilação pelo C2, o código nativo vai para o Code Cache — uma região de memória fora do heap Java, gerenciada separadamente. Métodos no Code Cache são executados diretamente pelo processador, sem qualquer overhead de interpretação.
Tamanhos padrão por versão
| Versão Java | Tamanho padrão | Observação |
|---|---|---|
| Java ≤ 7 (32-bit) | 32 MB | JVM 32-bit — removida no JDK 24 |
| Java ≤ 7 (64-bit) | 48 MB | — |
| Java 8+ (64-bit) | 240 MB | Padrão atual — adequado para a maioria |
❌ O aviso mais temido em produção
CodeCache is full. Compiler has been disabled.
A aplicação não cai. Mas o JIT para. Novos métodos não são compilados. Métodos podem ser desalojados e recompilados em loop. Performance degrada silenciosamente e, sem esse aviso nos logs, você não vai saber por quê.
Flags essenciais para diagnóstico e tuning
# Ver o que está sendo compilado em tempo real
-XX:+PrintCompilation
# Inspecionar tamanho atual do Code Cache
-XX:+PrintCodeCache
# Log completo para arquivo (útil em servidores remotos)
-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation
# Ajustar tamanho máximo do Code Cache
-XX:ReservedCodeCacheSize=256m
# Threads de compilação (1/3 para C1, 2/3 para C2)
-XX:CICompilerCount=6
# Threshold de invocações (só funciona sem Tiered Compilation)
-XX:CompileThreshold=10000
⚠️ Detalhe importante sobre o CompileThreshold
O
-XX:CompileThreshold=10000é amplamente citado mas quase nunca é o que a JVM usa. Com Tiered Compilation ativa (padrão desde o Java 8), a JVM usa thresholds por tier:
Tier3CompileThreshold = 2.000Tier4CompileThreshold = 15.000O
CompileThresholdgenérico só é relevante quando você desativa a Tiered Compilation com-XX:-TieredCompilation.
Seção 5 — GraalVM
Um terceiro compilador e, uma alternativa radical ao JIT
O GraalVM não é "só uma JVM mais rápida". É uma plataforma com duas abordagens fundamentalmente diferentes — que você precisa entender separadamente.
5.1 Graal JIT — substituto do C2, mesmo modelo JIT
O compilador Graal pode substituir o C2 dentro da HotSpot JVM usando a interface JVMCI (disponível desde o Java 9). Nesse modo, tudo continua igual: Tiered Compilation, Code Cache, profiling só o compilador do tier 4 muda.
-XX:+UseJVMCICompiler # ativa o Graal JIT no lugar do C2
O Graal JIT é escrito em Java (não em C++ como o C2), o que permite otimizações mais sofisticadas e facilita evolução. Em benchmarks específicos, ele supera o C2 em throughput de pico.
5.2 Native Image — AOT total, sem JVM em runtime
A opção mais radical do GraalVM: compilação AOT (Ahead-Of-Time) completa. O bytecode vira um executável nativo antes da execução. Não há JVM em runtime. Não há JIT. Não há warmup.
# Gerar executável nativo
native-image -jar minha-aplicacao.jar
# Resultado: binário nativo (~50ms startup, memória mínima)
Comparativo: JIT clássico vs GraalVM Native Image
| Característica | JIT (HotSpot padrão) | GraalVM Native Image |
|---|---|---|
| Startup | Segundos (warmup necessário) | ~50ms (instantâneo) |
| Peak performance | Máxima — C2 adaptativo | Inferior — sem JIT em runtime |
| Uso de memória | Maior (JVM + heap) | Muito menor |
| Reflexão dinâmica | ✅ Suportada nativamente | ⚠️ Requer reflect-config.json
|
| Spring Boot simples | ✅ Funciona out of the box | ✅ Funciona (GraalVM plugin) |
| Spring Boot complexo | ✅ Funciona sempre | ⚠️ Pode precisar de ajustes |
| Caso ideal | APIs, microsserviços com estado | Serverless, CLIs, functions |
| Build time | Padrão | Muito mais longa (5–15 min) |
❌ Por que Native Image tem peak performance menor?
O JIT é adaptativo: ele observa o comportamento real em runtime e aplica otimizações especulativas baseadas nesse comportamento. O Native Image compila tudo antes, sem dados de runtime. Otimizações como devirtualization especulativa e inlining agressivo de chamadas polimórficas ficam comprometidas. Para cargas variáveis e código polimórfico, o C2 vence no longo prazo.
Seção 6 — Project Leyden
A solução definitiva para o warmup — sem abrir mão do JIT
O warmup do JIT sempre foi o calcanhar de Aquiles do Java em ambientes cloud-native. Containers que sobem a cada scale-out, funções serverless, pods Kubernetes, todos começam do zero, sem cache JIT. O Project Leyden resolve isso.
Como funciona — em dois passos
Passo 1 — Training Run (executado uma única vez)
A JVM roda a aplicação normalmente e salva em um arquivo .aot tudo que normalmente faria no startup e warmup: classes carregadas e linkadas, perfis de invocação, código JIT compilado para os hot spots identificados.
Passo 2 — Execuções de Produção (usam o cache)
Todos os starts subsequentes carregam o cache. Classes já linkadas, hot spots já identificados, código C2 já compilado para os métodos críticos. A aplicação começa com performance de pico desde o primeiro request — sem warmup.
# Passo 1: Training run (uma vez, fora de produção)
java -XX:AOTCacheOutput=app.aot -jar app.jar
# Passo 2: Todas as execuções em produção
java -XX:AOTCache=app.aot -jar app.jar
# Spring Boot 3.3+ já integra esse workflow nativamente
Linha do tempo dos JEPs do Leyden
| Versão | Ano | JEP | O que entrega |
|---|---|---|---|
| JDK 24 | 2024 | JEP 483 | AOT Class Loading & Linking — classes pre-carregadas. Spring PetClinic 42% mais rápido no startup. |
| JDK 25 | 2025 | JEP 515 | AOT Method Profiling — perfis JIT no cache. Warmup praticamente eliminado. |
| JDK 26 | 2026 | JEP 516 | Cache universal, agnóstico de GC. Suporte a ZGC e Shenandoah. Cache baseline incluído no JDK — zero config. |
Comparativo final: os três mundos
┌──────────────────────────┬────────────────────────────┬──────────────────────────┐
│ JIT CLÁSSICO │ GRAALVM NATIVE IMAGE │ PROJECT LEYDEN │
│ (HotSpot) │ │ (JDK 25+) │
├──────────────────────────┼────────────────────────────┼──────────────────────────┤
│ ✓ Portabilidade total │ ✓ Startup instantâneo │ ✓ Startup ~42% mais │
│ ✓ Peak performance max │ ✓ Memória mínima │ rápido │
│ ✓ Reflexão dinâmica │ ✓ Ideal para serverless │ ✓ Warmup eliminado │
│ ✓ Sem mudanças no código │ │ ✓ Peak performance max │
│ │ ✗ Sem JIT em runtime │ ✓ Reflexão dinâmica │
│ ✗ Startup lento │ ✗ Peak performance menor │ ✓ Sem mudanças no código │
│ ✗ Warmup (código frio) │ ✗ Reflexão = config manual │ │
│ │ ✗ Build demorada │ ✗ Requer training run │
└──────────────────────────┴────────────────────────────┴──────────────────────────┘
✅ A frase que resume tudo
GraalVM Native Image substitui o JIT — você abre mão de otimizações adaptativas em troca de startup instantâneo.
Project Leyden preserva o JIT — você tem startup rápido e peak performance máxima, sem abrir mão de nada.
Para sistemas bancários com Spring Boot, Hibernate e cargas variáveis, Leyden é a resposta certa.
Seção 7 — Quando Parar de se Preocupar com Warmup
A resposta direta, versão por versão
| Versão | O que muda | Impacto real no warmup |
|---|---|---|
| Java 8 | Tiered Compilation padrão | JIT mais inteligente. Warmup ainda existe, mas bem gerenciado. |
| Java 11 | AppCDS estável | Startup ~10–20% mais rápido. Warmup JIT inalterado. |
| Java 17 | AppCDS aprimorado + ZGC estável | Startup melhorado. Warmup JIT inalterado. |
| Java 21 | Virtual Threads estáveis | ⚠️ NÃO resolve warmup. Resolve throughput I/O-bound. São problemas diferentes. |
| Java 24 | Leyden: AOT Class Loading | Startup 40%+ mais rápido. Warmup parcialmente melhorado. |
| Java 25 | Leyden: AOT Method Profiling | ✅ WARMUP PRATICAMENTE ELIMINADO. JIT começa otimizando do primeiro request. |
| Java 26 | AOT Cache universal + ZGC | Zero configuração extra. Cache baseline no JDK. Solução madura e completa. |
✅ Resposta direta: Java 25+ com AOT Method Profiling (JEP 515)
A partir do Java 25, o warmup do JIT deixa de ser uma preocupação para a grande maioria das aplicações. O AOT Cache armazena os perfis de execução dos métodos quentes, permitindo que o C2 compile os hot spots imediatamente no startup, sem precisar observar execuções repetidas. Para quem usa Spring Boot 3.3+, o suporte já está integrado com configuração mínima.
Contexto prático para Kubernetes e auto-scaling
Sem Leyden:
- Cada novo pod sobe sem cache JIT
- Os primeiros requests recebem latências elevadas
- Em ambientes com escala horizontal frequente, isso impacta diretamente o p95 e p99 de latência
Com Leyden (JDK 25+):
- O pod sobe com o AOT Cache
- Hot spots já identificados, código C2 já compilado
- Performance de pico desde o primeiro request
- O AOT Cache pode ser incluído na imagem Docker — sem step extra em runtime
Para ambientes bancários com Spring Boot + Kubernetes: JDK 25 com AOT Method Profiling é o caminho. Não exige mudanças no código, preserva todo o ecossistema Spring, mantém a performance máxima do C2 e elimina o problema de cold start que existe hoje.
Seção 8 — Linha do Tempo e Resumo Executivo
Evolução completa da performance JVM
| Versão | Ano | Marco |
|---|---|---|
| Java 1.0 | 1996 | Interpretado puro — 10–20× mais lento que C |
| Java 1.1 | 1997 | Primeiro JIT experimental — ainda muito básico |
| Java 1.3 | 2000 | HotSpot JVM como padrão — JIT adaptativo com C1 e C2 |
| Java 7 | 2011 | Tiered Compilation disponível como opção |
| Java 8 | 2014 | Tiered Compilation padrão — C1+C2 automático |
| Java 9 | 2017 | JVMCI: suporte ao Graal JIT como alternativa ao C2 |
| Java 11 | 2018 | AppCDS estável — startup melhorado |
| Java 21 | 2023 | Virtual Threads estáveis — throughput I/O-bound resolvido |
| Java 24 | 2024 | Project Leyden: AOT Class Loading — startup 42% mais rápido |
| Java 25 | 2025 | Project Leyden: AOT Method Profiling — warmup eliminado na prática |
| Java 26 | 2026 | AOT Cache universal + ZGC — solução madura e completa |
Resumo executivo — referência rápida
| Conceito | O que você precisa saber |
|---|---|
| JIT | A JVM compila hot spots para código nativo em runtime, em thread separada, sem parar a aplicação. |
| Hot spot | Método com alto número de invocações ou iterações de loop — candidato à compilação JIT. |
| C1 (tiers 1–3) | Compila rápido com otimizações básicas. Coleta profiling para o C2. |
| C2 (tier 4) | Compila profundo com otimizações agressivas: inlining, escape analysis, devirtualization, loop unrolling. |
| Tiered Compilation | Estratégia padrão desde Java 8: usa C1 primeiro (startup rápido) e C2 depois (peak performance). |
| Code Cache | Região de memória onde vive o código nativo compilado. 240 MB padrão no Java 8+. Cheio = problema grave. |
| Deoptimização | JVM joga fora a versão C2 se o comportamento mudar. Normal, mas frequência alta indica código inconsistente. |
| Graal JIT | Substituto do C2 escrito em Java. Mesmo modelo JIT, otimizações diferentes. Ativo via -XX:+UseJVMCICompiler. |
| GraalVM Native Image | AOT total: sem JVM em runtime, sem warmup, mas sem JIT adaptativo. Ideal para serverless/CLIs. |
| Project Leyden | AOT Cache: treina uma vez, usa sempre. JDK 25 elimina warmup sem abrir mão do JIT ou do código atual. |
| Quando resolver | Java 25+ para enterprise/Spring. GraalVM Native Image para serverless/CLIs simples. |
GraalVM vs sem GraalVM — análise com dados reais
O que os dados dizem sobre adoção em produção
Antes de entrar nos cenários, é importante calibrar as expectativas com números verificados.
O relatório New Relic State of the Java Ecosystem 2024 — baseado em dados de observabilidade de centenas de milhares de aplicações em produção — não registrou GraalVM Native Image como uma categoria de uso relevante entre as aplicações monitoradas. O relatório rastreia versões de JDK, vendors e GCs, mas o Native Image sequer aparece como item mensurável, o que por si só indica adoção marginal no universo enterprise geral. ¹
O relatório Azul State of Java 2025 — com mais de 2.000 profissionais Java respondentes globalmente, confirma que 99% das organizações pesquisadas utilizam Java ativamente, com quase 70% delas tendo mais da metade de suas aplicações rodando na JVM. O foco absoluto das empresas continua sendo JVM clássica com HotSpot. ²
O InfoQ Java Trends Report 2024 classifica o GraalVM Native Image para startup rápido na categoria "Early Majority" o que significa que a tecnologia existe e funciona, mas ainda não é mainstream. Para contexto, "Early Majority" no modelo de adoção de tecnologia representa algo entre 16% e 50% do mercado potencial, mas isso descreve o estágio de maturidade da tecnologia, não sua penetração real em produção. ³
Um dado de produção concreto vem do blog BackendBytes (fevereiro de 2026), que documentou migrações reais de equipes Spring Boot para Native Image: startup caindo de 7 segundos para 80ms, memória de 480 MB para 130 MB, mas com throughput em steady state 10 a 25% menor em comparação ao JIT. Esse número de queda de throughput é consistente entre múltiplas equipes migradas. ⁴
Um dado importante de contexto estratégico: em setembro de 2025, a Oracle anunciou o desacoplamento do GraalVM do Java SE, descontinuando o GraalVM Native Image para clientes do Java SE Product (Oracle JDK pago). O argumento oficial é que os objetivos de startup rápido e menor footprint serão perseguidos pelo Project Leyden como parte padrão do OpenJDK. Clientes usando o Graal JIT foram orientados a migrar de volta ao HotSpot JIT padrão. ⁵
O GraalVM Native Image continua disponível pela GraalVM Community Edition (mantida pela Oracle Labs e pela comunidade), mas o sinal estratégico é claro: a Oracle apostou no Leyden como caminho padrão, não no Native Image.
Os três cenários com benefícios e trade-offs reais
Cenário 1 — Sem GraalVM: HotSpot JIT puro
É o padrão atual de produção da grande maioria das aplicações Java no mundo. Você usa o C1 e o C2 da HotSpot, com Tiered Compilation ativa desde o Java 8.
Benefícios:
- Peak performance máxima no longo prazo, o C2 faz otimizações adaptativas impossíveis de reproduzir em AOT
- Funciona com qualquer biblioteca, framework ou padrão de reflexão sem configuração extra
- Todo o ecossistema Spring, Hibernate, Jackson, Kafka funciona sem ressalvas
- Deoptimização e recompilação automáticas quando o comportamento do código muda
- Suporte e documentação amplíssimos, é a JVM que todos conhecem
- Build time idêntico ao padrão, nenhuma etapa adicional
Trade-offs:
- Startup lento: aplicações Spring Boot complexas levam de 4 a 11 segundos para estar prontas
- Warmup necessário: os primeiros requests são mais lentos enquanto o JIT "aprende"
- Em Kubernetes com auto-scaling, cada novo pod começa do zero sem cache JIT
- Uso de memória maior, a JVM, o heap e o Code Cache consomem mais RAM
Quando usar: APIs enterprise, microsserviços com Spring Boot complexo, sistemas com Hibernate ou qualquer uso intenso de reflexão, cargas variáveis onde o C2 adaptativo faz diferença. Ou seja: a maioria das aplicações bancárias em produção hoje.
Cenário 2 — GraalVM modo JIT (Graal no lugar do C2)
A JVM continua sendo a HotSpot. Tiered Compilation, Code Cache, profiling, tudo igual. Só o compilador do tier 4 muda: o C2 é substituído pelo Graal, ativado com -XX:+UseJVMCICompiler.
Benefícios:
- Comportamento idêntico para o desenvolvedor, nenhuma mudança no código ou no processo de build
- O compilador Graal é escrito em Java (não em C++), o que permite algoritmos de otimização mais sofisticados
- Em workloads específicos com código muito regular e previsível, pode superar o C2 em throughput de pico
- Mantém todas as vantagens do JIT: deoptimização, profiling adaptativo, reflexão dinâmica
Trade-offs:
- Em setembro de 2025, a Oracle orientou explicitamente que clientes do Oracle JDK migrem de volta ao HotSpot JIT padrão, sinalizando que esse caminho não é o futuro ⁵
- Benefício de performance não é garantido, em muitos workloads o desempenho é equivalente ou ligeiramente inferior ao C2
- Menos testado em produção que o C2, ecossistema de suporte mais restrito
- O Graal JIT continua disponível na GraalVM CE, mas fora do mainstream Oracle
Quando usar: experimentos de performance em workloads muito específicos, pesquisa, ou contextos onde você já usa GraalVM CE e quer avaliar. Não recomendado como estratégia padrão de produção após o desacoplamento anunciado pela Oracle.
Cenário 3 — GraalVM Native Image (AOT total)
O bytecode vira um executável nativo antes de rodar. Não há JVM em runtime. Não há JIT. Não há warmup.
Benefícios:
- Startup instantâneo: de 4–11 segundos para 40–120ms em migrações documentadas ⁴
- Uso de memória drasticamente menor: redução de 60–70% em casos reais (480 MB → 130 MB) ⁴
- Ideal para ambientes com SLA de startup agressivo no Kubernetes, resolve o problema de pods perdendo a janela de readiness
- Binário auto-contido: não depende de JVM instalada no ambiente de execução
- Superfície de ataque reduzida: código morto removido na análise estática de build time
- Menor custo em funções serverless cobradas por tempo de execução e memória
Trade-offs:
- Throughput em steady state 10–25% menor que o JIT em produção real, dado documentado em múltiplas migrações ⁴
- A "closed-world assumption" exige que todo o código acessível seja conhecido em build time, reflexão dinâmica, proxies e carregamento dinâmico de classes precisam de configuração explícita
- Spring Boot, Hibernate, Jackson e similares funcionam, mas exigem hints de reflexão no
reflect-config.jsonou anotações específicas, o Spring Native/GraalVM plugin automatiza parte disso, mas não tudo - Build time muito mais longa: de 5 a 15 minutos para aplicações médias, dependendo do tamanho do classpath
- Debugging em produção é mais limitado, sem JVM, sem JVMTI, ferramentas como JFR e Async Profiler têm suporte parcial
- Sem deoptimização: se o comportamento mudar, não há mecanismo de correção automática em runtime
- A descontinuação para clientes Oracle JDK cria incerteza sobre o suporte de longo prazo fora da GraalVM CE ⁵
Quando usar: serviços serverless (AWS Lambda, Google Cloud Run), CLIs distribuídas como executável, microsserviços muito simples sem reflexão complexa, ambientes com SLA de startup impossível de cumprir com JVM clássica.
Decisão rápida
Sua aplicação é...
API enterprise / Spring Boot complexo / reflexão / carga variável?
└─► HotSpot JIT puro (+ Leyden no JDK 25+ para resolver o warmup)
Serverless / CLI / startup crítico / memória muito restrita?
└─► GraalVM Native Image (com validação cuidadosa de reflexão)
Quero experimentar Graal JIT sem mudar nada?
└─► -XX:+UseJVMCICompiler (mas avalie o desacoplamento Oracle antes)
Referências Bibliográficas
[1] NEW RELIC. 2024 State of the Java Ecosystem Report. San Francisco: New Relic, 2024. Disponível em: https://newrelic.com/resources/report/2024-state-of-the-java-ecosystem. Acesso em: jun. 2025.
[2] AZUL SYSTEMS. Azul 2025 State of Java Survey & Report. Sunnyvale: Azul, jan. 2025. Disponível em: https://www.azul.com/newsroom/azul-2025-state-of-java-survey-report/. Acesso em: jun. 2025.
[3] INFOQ EDITORIAL TEAM. InfoQ Java Trends Report — December 2024. InfoQ, dez. 2024. Disponível em: https://www.infoq.com/articles/java-trends-report-2024/. Acesso em: jun. 2025.
[4] BACKENDBYTES. GraalVM Native Images in Production: From 5-Second Startup to 50ms. BackendBytes, fev. 2026. Disponível em: https://backendbytes.com/articles/graalvm-native-images-java-production/. Acesso em: jun. 2025.
[5] ORACLE. Detaching GraalVM from the Java Ecosystem Train. Oracle Java Blog, set. 2025. Disponível em: https://blogs.oracle.com/java/detaching-graalvm-from-the-java-ecosystem-train. Acesso em: jun. 2025.
[6] OPENJDK. JEP 483: Ahead-of-Time Class Loading & Linking. OpenJDK, 2024. Disponível em: https://openjdk.org/jeps/483. Acesso em: jun. 2025.
[7] OPENJDK. JEP 514: Ahead-of-Time Command-Line Ergonomics. OpenJDK, 2025. Disponível em: https://openjdk.org/jeps/514. Acesso em: jun. 2025.
[8] OPENJDK. JEP 515: Ahead-of-Time Method Profiling. OpenJDK, 2025. Disponível em: https://openjdk.org/jeps/515. Acesso em: jun. 2025.
[9] OPENJDK. JEP 516: Ahead-of-Time Cache. OpenJDK, 2026. Disponível em: https://openjdk.org/jeps/516. Acesso em: jun. 2025.
[10] BAELDUNG. JVM Tiered Compilation. Baeldung, 2024. Disponível em: https://www.baeldung.com/jvm-tiered-compilation. Acesso em: jun. 2025.
[11] ORACLE GRAAL TEAM. Announcing Oracle GraalVM for JDK 24. Oracle Graal Blog, 2024. Disponível em: https://blogs.oracle.com/graal/oracle-graalvm-for-jdk-24. Acesso em: jun. 2025.
[12] GRAALVM. Native Image Documentation. GraalVM, 2025. Disponível em: https://www.graalvm.org/latest/docs/. Acesso em: jun. 2025.
[13] INFOQ EDITORIAL TEAM. InfoQ Java Trends Report 2025. InfoQ, dez. 2025. Disponível em: https://www.infoq.com/articles/java-trends-report-2025/. Acesso em: jun. 2025.
Documento de estudo pessoal · Diego Brandão · 2025
Top comments (0)