DEV Community

Diego de Sousa Brandão
Diego de Sousa Brandão

Posted on

JIT Compilation — Guia Didático

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

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
Enter fullscreen mode Exit fullscreen mode

O símbolo % no output de -XX:+PrintCompilation indica 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 Integer agora recebe String), 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 como made not entrant e 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

⚠️ 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.000
  • Tier4CompileThreshold = 15.000

O CompileThreshold gené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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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    │
└──────────────────────────┴────────────────────────────┴──────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

✅ 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.json ou 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)
Enter fullscreen mode Exit fullscreen mode

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)