JIT (Just-In-Time) é uma tecnologia de compilação dinâmica que converte bytecode ou representação intermediária de programa em instruções de máquina diretamente durante a execução. No Android, o compilador JIT apareceu pela primeira vez na versão 2.2 Froyo como parte da máquina virtual Dalvik e acelerou a execução de aplicativos em 2–5 vezes. De acordo com Google, 2024, o JIT moderno no ART combina interpretação com compilação perfilada de métodos hot.
Principais conclusões
Just-In-Time (JIT) é um método de compilação no qual o código fonte ou bytecode é convertido em instruções de máquina não antecipadamente (como no AOT), mas no momento da primeira chamada à seção correspondente do programa. O termo “Just-In-Time” significa que a compilação ocorre “na hora certa” — imediatamente antes da execução.
O conceito de JIT existe desde a década de 1960, mas ganhou ampla adoção com o advento da Máquina Virtual Java em 1995. O JIT permite combinar a portabilidade do bytecode (escreva uma vez — execute em qualquer lugar) com desempenho próximo ao código nativo. Na JVM HotSpot, o compilador JIT analisa o código executado e compila apenas as seções mais críticas, economizando tempo e memória.
O compilador JIT recebe bytecode como entrada, interpreta-o e simultaneamente coleta estatísticas. Quando uma seção de código (método, loop) é chamada com frequência suficiente, o JIT decide compilá-la. O código de máquina compilado é armazenado em cache — em chamadas subsequentes, a versão já compilada é usada. Isso fornece aceleração sem ter que compilar o programa inteiro.
// Exemplo: um método se torna hot após múltiplas chamadas
public class HotMethod {
private int compute(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i * i;
}
return sum;
}
}
// Chamar 500 vezes em um loop — JIT compilará compute
for (int t = 0; t < 500; t++) {
hot.compute(1000);
}
No Android, a compilação JIT passou por três fases de evolução. A primeira fase — Dalvik sem JIT (Android 1.0–2.1): interpretação pura do bytecode DEX. A segunda fase — Dalvik com JIT (Android 2.2–4.4): a introdução do compilador JIT, que acelerou os aplicativos em 2–5 vezes. A terceira fase — ART com JIT híbrido (Android 7.0+): o retorno do JIT em uma nova capacidade.
O JIT no Dalvik foi implementado como um compilador baseado em traços. Ele analisava não métodos individuais, mas cadeias de instruções (traços) que são executadas sequencialmente com frequência. Isso permitia compilar caminhos de execução completos, incluindo vários métodos. Essa abordagem era eficaz para processadores móveis com pequenos caches de instrução, pois o traço compilado cabia no cache L1.
A partir do Android 7.0 Nougat, o ART usa JIT baseado em métodos — compila métodos individuais com base em perfis de execução. Este JIT funciona significativamente mais rápido que o Dalvik JIT: o tempo típico de compilação de um método é de 0.5–1 ms contra 3–5 ms no Dalvik. O código compilado é armazenado em uma área de memória separada (cache de código JIT) em vez do heap do aplicativo, o que reduz a fragmentação.
| Parâmetro | Dalvik JIT | ART JIT |
|---|---|---|
| Tipo | Baseado em traços | Baseado em métodos |
| Velocidade de compilação | 3–5 ms/método | 0.5–1 ms/método |
| Limite de compilação | ~200 chamadas | Dinâmico |
| Cache de código | No heap do aplicativo | Cache de código JIT |
| Perfilamento | Interno | Arquivos .prof externos |
O mecanismo central do JIT é a detecção de métodos hot. Cada chamada de método incrementa um contador interno. Quando o contador ultrapassa o limite, o método é marcado como “hot” e enviado para compilação. No Dalvik, o limite era fixo (~200 chamadas). No ART, os contadores são configurados dinamicamente dependendo dos recursos disponíveis do dispositivo.
O processo de compilação inclui várias fases. A primeira — análise de bytecode: o JIT examina o fluxo de instruções e constrói um grafo de fluxo de dados. A segunda — otimização: inlining de métodos pequenos, eliminação de código morto, dobramento de constantes. A terceira — geração de código: conversão do grafo otimizado em instruções de máquina para uma arquitetura de CPU específica (ARM, ARM64, x86).
// Demonstração de inlining — JIT inlineará o corpo do método
public int inlineExample() {
return square(5);
}
private int square(int x) {
return x * x;
} // JIT substituirá a chamada por return 5 * 5;
Uma técnica especial do JIT — On-Stack Replacement (OSR). Se um método contém um loop longo que não termina por centenas de iterações, o JIT pode compilar o loop “sob demanda” e substituir a versão interpretada pela compilada diretamente durante a execução. O OSR é especialmente eficaz para tarefas computacionais: renderização, processamento de imagens, criptografia.
JIT e AOT são duas abordagens de compilação com compromissos opostos. O JIT sacrifica a velocidade do primeiro lançamento por um tamanho de distribuição compacto e adaptabilidade. O AOT sacrifica o tempo de instalação e o espaço em disco pelo máximo desempenho desde o primeiro segundo. Nenhuma abordagem é absolutamente melhor — a escolha depende do cenário.
A principal vantagem do JIT é a otimização adaptativa. O JIT pode usar informações de perfil indisponíveis para o AOT: tipos exatos de objetos, frequência real de chamadas, padrões reais de ramificação. Isso permite aplicar otimizações agressivas impossíveis com a compilação estática. Por exemplo, o JIT pode desvirtualizar chamadas de método se apenas um tipo de receptor for encontrado na prática.
| Critério | JIT | AOT |
|---|---|---|
| Tempo de instalação | Instantâneo | Depende do tamanho |
| Primeiro lançamento | Mais lento (aquecimento) | Rápido |
| Espaço em disco | Mínimo | +15–30% |
| Adaptabilidade | Alta | Baixa |
| Uso de CPU | Picos durante compilação | Estável |
A compilação JIT é preferível quando a implantação rápida e a economia de espaço em disco são importantes. No contexto do desenvolvimento mobile, o JIT é ideal para aplicativos que são atualizados com frequência (testes A/B, hotfixes). O JIT também é conveniente durante o desenvolvimento, quando o código é reconstruído dezenas de vezes ao dia — cada segundo economizado na compilação acelera o ciclo de feedback.
JIT fornece aos desenvolvedores uma série de vantagens práticas. A primeira — tamanho pequeno do APK. Com a abordagem JIT, apenas o bytecode (DEX) é empacotado no APK, que ocupa 20–30% menos espaço que o código nativo compilado. Para usuários com armazenamento interno limitado, esta é uma vantagem significativa.
A segunda vantagem é a adaptação ao dispositivo. O JIT compila código levando em conta a arquitetura real da CPU, a quantidade de RAM e a carga atual. Por exemplo, em um dispositivo com 2 GB de RAM, o JIT pode compilar de forma menos agressiva para economizar memória, enquanto em um flagship com 12 GB, pode aplicar todas as otimizações possíveis. A compilação AOT, por outro lado, fixa a decisão no momento da instalação.
O bytecode permanece independente de plataforma, o que simplifica a distribuição de aplicativos. Um único APK funciona em dispositivos ARM, ARM64 e x86, e o JIT gera código nativo para cada arquitetura. A abordagem AOT exigiria incluir múltiplas variantes de código nativo no APK (aumentando o tamanho) ou compilar uma versão separada para cada arquitetura.
A principal desvantagem do JIT é o atraso de aquecimento. O usuário experimenta lentidão nos primeiros segundos do aplicativo enquanto o JIT compila os métodos hot. Em jogos, isso se manifesta como gaguejo nos níveis iniciais. Em aplicativos com animações — transições bruscas entre telas.
A segunda desvantagem — consumo de energia. O processo de compilação carrega intensamente a CPU, aumentando o consumo de energia em 10–20% durante o período de aquecimento. Em dispositivos com bateria, isso reduz a autonomia. É especialmente perceptível em cenários com reinicializações frequentes de aplicativos (multitarefa com memória limitada, onde o sistema descarrega e recarrega processos).
Outro problema — a fragmentação do cache JIT. O código compilado é armazenado em uma área contínua de memória. Quando novas classes são carregadas e métodos adicionais são compilados, o cache se fragmenta, aumentando a sobrecarga de gerenciamento de memória. No Dalvik, esse problema era resolvido com a limpeza periódica do cache; no ART, o cache JIT é alocado separadamente do heap e usa sua própria estratégia de desfragmentação.
A abordagem moderna no ART — a compilação híbrida, combinando os pontos fortes do JIT e do AOT. Durante a instalação do aplicativo, nenhuma compilação é realizada — apenas verificação de bytecode (verify). Isso garante instalação rápida e uso mínimo de espaço. Os primeiros lançamentos ocorrem em modo de interpretação com compilação JIT de métodos hot — o usuário obtém desempenho aceitável sem longos tempos de espera.
Simultaneamente, um perfilador em segundo plano coleta dados sobre o uso real. Após 2–3 lançamentos completos do aplicativo, o perfil atinge completude suficiente, e o sistema executa o dex2oat para compilar os métodos hot em código nativo. Esta operação é realizada em segundo plano quando o dispositivo não está carregado (carregando, tela desligada). Após a conclusão do AOT em segundo plano, o aplicativo atinge desempenho comparável à compilação AOT completa.
# Início forçado de compilação em segundo plano
adb shell cmd package compile -m speed-profile -f com.example.app
# Ver status da compilação
adb shell cmd package dump-profiles com.example.app
De acordo com o Google I/O 2017, a compilação híbrida reduziu o tempo de instalação de aplicativos em 30–50% em comparação com o AOT puro. O espaço ocupado na partição do sistema diminuiu 20–30%. Ao mesmo tempo, o desempenho após a compilação em segundo plano corresponde ao nível do AOT completo. O único cenário onde o híbrido fica aquém do AOT é o primeiro lançamento imediatamente após a instalação: o aplicativo funciona em modo JIT e pode ser 10–15% mais lento.
Perguntas frequentes
JIT é uma forma de acelerar um programa onde o código é traduzido para linguagem de máquina não antecipadamente, mas em partes durante a execução. As seções mais frequentes são compiladas e armazenadas em cache, enquanto as raras permanecem em sua forma original.
JIT compila código durante a execução, economizando espaço e acelerando a instalação. O AOT compila todo o código antecipadamente — o aplicativo inicia mais rápido, mas requer mais espaço em disco e tempo de instalação.
O JIT não foi removido — ele evoluiu. No Android 5.0, o Dalvik com JIT foi substituído pelo ART com AOT puro. No Android 7.0, o JIT retornou ao ART como parte de um sistema híbrido onde trabalha junto com a compilação AOT em segundo plano para desempenho ideal.
JIT aumenta o consumo de energia em 10–20% durante o período de aquecimento devido à carga da CPU. Após a conclusão da compilação dos métodos hot, o consumo de energia retorna aos níveis normais. O modo híbrido do ART minimiza esses picos através da compilação em segundo plano.
Sim, em cenários com cálculos intensivos. O usuário pode notar lentidão nos primeiros segundos de operação do aplicativo ou no início de um jogo. Nas versões modernas do Android (8.0+), o modo híbrido minimiza esse efeito graças à compilação perfilada.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também