JIT (Just-In-Time) es una tecnología de compilación dinámica que convierte el bytecode o la representación intermedia de un programa en instrucciones de máquina directamente durante la ejecución. En Android, el compilador JIT apareció por primera vez en la versión 2.2 Froyo como parte de la máquina virtual Dalvik y aceleró la ejecución de aplicaciones entre 2 y 5 veces. Según Google, 2024, el JIT moderno en ART combina la interpretación con la compilación perfilada de métodos hot.
Puntos clave
Just-In-Time (JIT) es un método de compilación en el que el código fuente o bytecode se convierte en instrucciones de máquina no por adelantado (como con AOT), sino en el momento de la primera llamada a la sección correspondiente del programa. El término “Just-In-Time” significa que la compilación ocurre “justo a tiempo” — inmediatamente antes de la ejecución.
El concepto de JIT existe desde la década de 1960, pero ganó adopción generalizada con la llegada de la Máquina Virtual de Java en 1995. JIT permite combinar la portabilidad del bytecode (escribe una vez — ejecuta en cualquier lugar) con un rendimiento cercano al código nativo. En la JVM HotSpot, el compilador JIT analiza el código ejecutado y compila solo las secciones más críticas, ahorrando tiempo y memoria.
El compilador JIT recibe bytecode como entrada, lo interpreta y simultáneamente recopila estadísticas. Cuando una sección de código (método, bucle) se llama con suficiente frecuencia, JIT decide compilarla. El código máquina compilado se almacena en un caché — en llamadas posteriores, se utiliza la versión ya compilada. Esto proporciona aceleración sin tener que compilar todo el programa.
// Ejemplo: un método se vuelve hot después de múltiples llamadas
public class HotMethod {
private int compute(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i * i;
}
return sum;
}
}
// Llamar 500 veces en un bucle — JIT compilará compute
for (int t = 0; t < 500; t++) {
hot.compute(1000);
}
En Android, la compilación JIT pasó por tres fases de evolución. La primera fase — Dalvik sin JIT (Android 1.0–2.1): interpretación pura del bytecode DEX. La segunda fase — Dalvik con JIT (Android 2.2–4.4): la introducción del compilador JIT, que aceleró las aplicaciones entre 2 y 5 veces. La tercera fase — ART con JIT híbrido (Android 7.0+): el regreso de JIT en una nueva capacidad.
JIT en Dalvik se implementó como un compilador basado en trazas. Analizaba no métodos individuales, sino cadenas de instrucciones (trazas) que se ejecutan con frecuencia secuencialmente. Esto permitía compilar rutas de ejecución completas, incluyendo múltiples métodos. Este enfoque era efectivo para procesadores móviles con cachés de instrucciones pequeñas, ya que la traza compilada cabía en el caché L1.
A partir de Android 7.0 Nougat, ART utiliza JIT basado en métodos — compila métodos individuales basándose en perfiles de ejecución. Este JIT funciona significativamente más rápido que Dalvik JIT: el tiempo típico de compilación de un método es de 0.5–1 ms en comparación con 3–5 ms en Dalvik. El código compilado se almacena en un área de memoria separada (caché de código JIT) en lugar del heap de la aplicación, lo que reduce la fragmentación.
| Parámetro | Dalvik JIT | ART JIT |
|---|---|---|
| Tipo | Basado en trazas | Basado en métodos |
| Velocidad de compilación | 3–5 ms/método | 0.5–1 ms/método |
| Umbral de compilación | ~200 llamadas | Dinámico |
| Caché de código | En el heap de la aplicación | Caché de código JIT |
| Perfilado | Interno | Archivos .prof externos |
El mecanismo central de JIT es la detección de métodos hot. Cada llamada a un método incrementa un contador interno. Cuando el contador cruza el umbral, el método se marca como “hot” y se envía a compilación. En Dalvik, el umbral era fijo (~200 llamadas). En ART, los contadores se configuran dinámicamente según los recursos disponibles del dispositivo.
El proceso de compilación incluye varias fases. La primera — análisis de bytecode: JIT examina el flujo de instrucciones y construye un grafo de flujo de datos. La segunda — optimización: inline de métodos pequeños, eliminación de código muerto, plegado de constantes. La tercera — generación de código: conversión del grafo optimizado en instrucciones de máquina para una arquitectura de CPU específica (ARM, ARM64, x86).
// Demostración de inlining — JIT integrará el cuerpo del método
public int inlineExample() {
return square(5);
}
private int square(int x) {
return x * x;
} // JIT reemplazará la llamada con return 5 * 5;
Una técnica especial de JIT — On-Stack Replacement (OSR). Si un método contiene un bucle largo que no termina durante cientos de iteraciones, JIT puede compilar el bucle “sobre la marcha” y reemplazar la versión interpretada por la compilada directamente durante la ejecución. OSR es especialmente efectivo para tareas computacionales: renderizado, procesamiento de imágenes, criptografía.
JIT y AOT son dos enfoques de compilación con compensaciones opuestas. JIT sacrifica la velocidad del primer inicio por un tamaño de distribución compacto y adaptabilidad. AOT sacrifica el tiempo de instalación y el espacio en disco por el máximo rendimiento desde el primer segundo. Ningún enfoque es absolutamente mejor — la elección depende del escenario.
La ventaja clave de JIT es la optimización adaptativa. JIT puede usar información de perfil no disponible para AOT: tipos exactos de objetos, frecuencia real de llamadas, patrones de ramificación reales. Esto permite aplicar optimizaciones agresivas imposibles con la compilación estática. Por ejemplo, JIT puede desvirtualizar llamadas a métodos si solo se encuentra un tipo de receptor en la práctica.
| Criterio | JIT | AOT |
|---|---|---|
| Tiempo de instalación | Instantáneo | Depende del tamaño |
| Primer inicio | Más lento (calentamiento) | Rápido |
| Espacio en disco | Mínimo | +15–30% |
| Adaptabilidad | Alta | Baja |
| Uso de CPU | Picos durante compilación | Estable |
La compilación JIT es preferible cuando son importantes el despliegue rápido y el ahorro de espacio en disco. En el contexto del desarrollo móvil, JIT es ideal para aplicaciones que se actualizan con frecuencia (pruebas A/B, parches en caliente). JIT también es conveniente durante el desarrollo, cuando el código se reconstruye docenas de veces al día — cada segundo ahorrado en compilación acelera el ciclo de retroalimentación.
JIT proporciona a los desarrolladores una serie de ventajas prácticas. La primera — tamaño pequeño del APK. Con el enfoque JIT, solo el bytecode (DEX) se empaqueta en el APK, que ocupa 20–30% menos espacio que el código nativo compilado. Para usuarios con almacenamiento interno limitado, esta es una ventaja significativa.
La segunda ventaja es la adaptación al dispositivo. JIT compila código teniendo en cuenta la arquitectura real de la CPU, la cantidad de RAM y la carga actual. Por ejemplo, en un dispositivo con 2 GB de RAM, JIT puede compilar de forma menos agresiva para ahorrar memoria, mientras que en un flagship con 12 GB puede aplicar todas las optimizaciones posibles. La compilación AOT, por otro lado, fija la decisión en el momento de la instalación.
El bytecode permanece independiente de la plataforma, lo que simplifica la distribución de aplicaciones. Un solo APK funciona en dispositivos ARM, ARM64 y x86, y JIT genera código nativo para cada arquitectura. El enfoque AOT requeriría incluir múltiples variantes de código nativo en el APK (aumentando el tamaño) o compilar una versión separada para cada arquitectura.
La principal desventaja de JIT es el retraso de calentamiento. El usuario experimenta ralentizaciones en los primeros segundos de la aplicación mientras JIT compila los métodos hot. En los juegos, esto se manifiesta como tartamudeo en los niveles iniciales. En aplicaciones con animaciones — transiciones bruscas entre pantallas.
La segunda desventaja — consumo de energía. El proceso de compilación carga intensamente la CPU, aumentando el consumo de energía entre un 10–20% durante el período de calentamiento. En dispositivos con batería, esto reduce la autonomía. Es especialmente notable en escenarios con reinicios frecuentes de aplicaciones (multitarea con memoria limitada, donde el sistema descarga y recarga procesos).
Otro problema — la fragmentación del caché JIT. El código compilado se almacena en un área de memoria continua. Cuando se cargan nuevas clases y se compilan métodos adicionales, el caché se fragmenta, aumentando la sobrecarga de gestión de memoria. En Dalvik, este problema se resolvía con la limpieza periódica del caché; en ART, el caché JIT se asigna por separado del heap y utiliza su propia estrategia de desfragmentación.
El enfoque moderno en ART — la compilación híbrida, que combina las fortalezas de JIT y AOT. Durante la instalación de la aplicación, no se realiza compilación — solo verificación de bytecode (verify). Esto garantiza una instalación rápida y un uso mínimo de espacio. Los primeros lanzamientos se ejecutan en modo de interpretación con compilación JIT de métodos hot — el usuario obtiene un rendimiento aceptable sin largos tiempos de espera.
Simultáneamente, un perfilador en segundo plano recopila datos sobre el uso real. Después de 2–3 lanzamientos completos de la aplicación, el perfil alcanza suficiente completitud, y el sistema ejecuta dex2oat para compilar los métodos hot en código nativo. Esta operación se realiza en segundo plano cuando el dispositivo no está cargado (cargando, pantalla apagada). Una vez completada la AOT en segundo plano, la aplicación alcanza un rendimiento comparable a la compilación AOT completa.
# Inicio forzado de compilación en segundo plano
adb shell cmd package compile -m speed-profile -f com.example.app
# Ver estado de compilación
adb shell cmd package dump-profiles com.example.app
Según Google I/O 2017, la compilación híbrida redujo el tiempo de instalación de aplicaciones entre un 30–50% en comparación con AOT pura. El espacio ocupado en la partición del sistema disminuyó entre un 20–30%. Al mismo tiempo, el rendimiento después de la compilación en segundo plano coincide con el nivel de AOT completa. El único escenario donde el híbrido es inferior a AOT es el primer inicio inmediatamente después de la instalación: la aplicación funciona en modo JIT y puede ser 10–15% más lenta.
Preguntas frecuentes
JIT es una forma de acelerar un programa donde el código se traduce a lenguaje máquina no por adelantado, sino en partes mientras se ejecuta. Las secciones más frecuentes se compilan y almacenan en caché, mientras que las raras permanecen en su forma original.
JIT compila código durante la ejecución, ahorrando espacio y acelerando la instalación. AOT compila todo el código por adelantado — la aplicación se inicia más rápido pero requiere más espacio en disco y tiempo de instalación.
JIT no se eliminó — evolucionó. En Android 5.0, Dalvik con JIT fue reemplazado por ART con AOT pura. En Android 7.0, JIT regresó a ART como parte de un sistema híbrido donde trabaja junto con la compilación AOT en segundo plano para un rendimiento óptimo.
JIT aumenta el consumo de energía entre un 10–20% durante el período de calentamiento debido a la carga de la CPU. Una vez completada la compilación de métodos hot, el consumo de energía regresa a niveles normales. El modo híbrido de ART minimiza estos picos mediante la compilación en segundo plano.
Sí, en escenarios con cálculos intensivos. El usuario puede notar ralentizaciones en los primeros segundos de funcionamiento de la aplicación o al inicio de un juego. En las versiones modernas de Android (8.0+), el modo híbrido minimiza este efecto gracias a la compilación perfilada.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también