The JVM is not a virtual machine in the sense of pretending to be another machine's hardware. It is a **runtime process that loads bytecode, verifies it, and executes it**, either by interpreting it or by compiling it to native code for the actual processor.
That distinction matters because "interpreted" is a common and inaccurate description of Java. Java source compiles to bytecode ahead of time, and then the JIT compiler translates hot bytecode to native machine code while the program runs. A long-running Java program is mostly running natively compiled code, not interpreted bytecode.
YOUR SOURCE THE JVM PROCESS
┌──────────┐ javac ┌──────────────────────────────┐
│ Foo.java│ ────────────> │ Foo.class (bytecode) │
└──────────┘ AOT │ loaded, verified, run │
│ │
│ interpreter ─┐ │
│ ├─> native │
│ JIT (C1, C2) ─┘ machine │
│ code │
│ GC ◄──────── heap │
└──────────────────────────────┘The concrete version of this process is observable. Running the example for this topic against Temurin 17 prints:
==1. CLASSLOADER SUBSYSTEM ==classes loaded so far : 688String.class loader : null(null = bootstrap)==2. RUNTIME DATA AREAS ==max heap(-Xmx) : 3920 MB==3. EXECUTION ENGINE ==JIT time at start : 37 msJIT time after hot loop: 55 ms== the stack is per-thread and bounded ==StackOverflowError after 21259 frames