Déjame explicarlo con el ejemplo de una aplicación Java de escritorio.
La aplicación se suele compilar desde el código fuente de Java a archivos de clase (bytecode) y se empaqueta en archivos Zip con una extensión de archivo especial ".jar" y se guarda en el disco duro (por ejemplo, el disco duro Seagate de 500GB).
El JAR se ejecuta con la ayuda de la Máquina Virtual Java (debería estar ya instalada) en el PC/Mac.
Se inicia una instancia de la JVM para cada aplicación Java. (La propia JVM es una aplicación nativa en el PC/Mac - por lo tanto, eso mismo es un programa que se carga y se ejecuta - no voy a cubrir eso aquí).
La JVM carga las clases de la aplicación una por una a medida que se necesitan - y las mantiene en la RAM. En esta etapa, todavía es bytecode.
La JVM lee fragmentos cortos de los próximos cientos de instrucciones de bytecode y los traduce justo a tiempo (JIT) a comandos de lenguaje ensamblador (comandos específicos de la CPU - por ejemplo, el conjunto de instrucciones de Intel Core i3/AMD Ryzen/Qualcomm Snapdragon) y los carga en las cachés de la CPU - caché L1/L2 en la propia CPU (por lo general alrededor de 128KB a 2MB). A partir de ahí, la CPU ejecutará las instrucciones una a una. Una vez que se completa una secuencia, el JIT colocará el siguiente conjunto de instrucciones para la CPU.
El JIT también tendrá en cuenta el Sistema Operativo mientras hace la conversión a instrucciones de la CPU.
Por ejemplo, si el programa Java está tratando de abrir un archivo - el bytecode sigue siendo el mismo, pero el JIT producirá un conjunto diferente de instrucciones para Windows y Linux para el mismo procesador Core i3.
El sistema JIT también almacena en caché algunos fragmentos convertidos de bytecode a ensamblador del código que se ejecuta con frecuencia, de modo que esas secciones funcionan a gran velocidad.
Las clases casi nunca se descargan de la RAM.
Los datos del programa (recursos, entradas del usuario, resultados de los cálculos, etc.) pueden cargarse en la RAM y descargarse a medida que se ejecuta el programa. Los cálculos se realizan en realidad moviendo los datos de la RAM a los registros de la CPU, haciendo los cálculos y luego moviendo los resultados de nuevo de los registros de la CPU a la RAM.
Todo esto se complica un poco más si se trata de un programa Java para un sistema embebido o una aplicación móvil Java.