Cloud Parts

Runtime virtual machines

A program that runs other programs: compilers emit portable bytecode, and the runtime loads it, compiles the busy parts to machine code, collects garbage and schedules work on whatever machine it lands on.

3 technologies on this page ↓

How it works

A compiler such as javac, the C# compiler or the Erlang compiler doesn’t emit machine code for one CPU. It emits instructions for an abstract machine: JVM bytecode in .class files, Common Intermediate Language (CIL) plus metadata in .NET assemblies, BEAM instructions in .beam files. The runtime is an ordinary process on the host operating system. It loads code while the program runs (the JVM loads, links and initialises classes dynamically), checks it (the JVM enforces structural constraints on class files; the CLR verifies that CIL is type safe), and turns it into machine code. HotSpot, the main JVM, starts by interpreting bytecode, then compiles methods that run often: first with a quick compiler whose code records a profile, then with an optimising compiler that uses that profile. .NET’s JIT compiler, RyuJIT, compiles each method on its first call, quickly and without optimisations, and recompiles hot methods with full optimisation in the background. BEAM’s JIT, BeamAsm, translates every instruction into native code as a module loads. The runtime also owns memory: objects live on heaps that a garbage collector reclaims, so programs never free memory themselves. Because the instruction set and libraries are the same everywhere, one compiled file runs on any operating system and CPU the runtime supports, and any language whose compiler emits that format can share its libraries: Java, Scala and Clojure on the JVM; C#, F# and Visual Basic on .NET; Erlang and Elixir on BEAM.

Think of it as

Sheet music. A composer writes one score instead of one recording per concert hall, and every orchestra plays it on its own instruments. A good orchestra notices which passages keep coming back and drills those until they’re fast, so the music speeds up as the evening goes on.

Reach for it when

  • One build has to run on several operating systems and CPU types without recompiling
  • Long-running servers, where the warm-up cost is paid once and profile-guided compilation pays off for days
  • You want a big ecosystem’s libraries from another language: Scala or Clojure calling Java code, F# calling C#
  • Many concurrent, isolated tasks, which BEAM’s lightweight processes or the JVM’s virtual threads handle well

Think twice when

  • Short-lived processes such as command-line tools or scale-to-zero functions, where start-up and warm-up dominate; compile ahead of time instead
  • Kernels, firmware and small embedded devices with no room for a runtime and a garbage collector
  • Code that must never pause unpredictably: even concurrent collectors pause briefly, and the JIT compiles in the middle of a run

A real example

A Spring PetClinic 3.2.0 service on the JVM loads and links about 21,000 classes every time it starts, and its busy methods run interpreted until the JIT has compiled them. OpenJDK measured a start of 4.486 seconds on JDK 23. JDK 24 can record the loaded and linked classes from a training run in an ahead-of-time cache (130 MB for this app), and the same service then starts in 2.604 seconds, 42% faster. Compiling the whole app to a native executable with GraalVM Native Image goes further: it starts in milliseconds with no warm-up, but only code that the build-time analysis can see is included, so reflection and dynamically loaded classes need extra configuration.

Technologies

Specific products, and what each does differently.

JVM, CLR and BEAM side by side#

The three runtimes most server code runs on, as of October 2026.

JVM (HotSpot).NET CLRBEAM (Erlang/OTP)
Compiled code.class files: bytecode plus a symbol tableCIL plus metadata in portable executable (PE) files.beam object files
Specified byThe Java Virtual Machine SpecificationECMA-335, the Common Language Infrastructure (6th edition, 2012; also ISO/IEC 23271)The Erlang/OTP implementation and its documentation
How code runsInterpreter first; tiered JIT: client compiler, then server compiler for hot methodsTiered JIT (RyuJIT): quick JIT or precompiled ReadyToRun code first, optimised recompile for hot methodsInterpreter, or the BeamAsm JIT, which translates each instruction to native code at load time (x86-64 and AArch64, since OTP 24)
Ahead-of-time optionGraalVM Native Image; since JDK 24, an AOT cache of loaded and linked classesNative AOT: no JIT at run time, no dynamic assembly loadingNot at build time: BeamAsm compiles each module as it loads
Unit of concurrencyOS threads, plus virtual threads since Java 21Threads, plus async/await over TasksLightweight processes: 327 words when spawned, switched out after 4,000 reductions, one scheduler thread per logical CPU by default
MemoryOne heap shared by all threads; G1 collector by default, ZGC for sub-millisecond pausesShared heap in generations 0, 1 and 2, plus a large object heap for objects of 85,000 bytes or moreA private heap per process, collected separately by a generational copying collector; messages are copied between heaps
LanguagesJava, Scala, Clojure and othersC#, F#, Visual BasicErlang, Elixir

Process VMs, system VMs and containers#

“Virtual machine” names two different things. A system virtual machine, such as a cloud server instance, emulates a whole computer and runs a complete operating system with its own kernel and drivers. A process virtual machine runs as one ordinary process inside an operating system, starts when that process starts and goes away when it exits. It doesn’t imitate hardware; it gives programs an instruction set and services (memory management, threads, class loading) that are the same on every platform.

A container is neither. It’s an isolated process (or group of processes) running on the host’s kernel, packaged with its files. The code inside is whatever it was before: native machine code, or a runtime VM. In practice they stack: a JVM is a process, inside a container, often inside a system VM in a cloud.

Where the line blurs#

A runtime VM isn’t necessarily an interpreter. The JVM specification says the JVM “is not inherently interpreted, but can just as well be implemented by compiling its instruction set to that of a silicon CPU”, and in practice HotSpot, RyuJIT and BeamAsm all run most of a long-lived program as machine code.

Ahead-of-time compilers go further and remove the JIT altogether. GraalVM Native Image and .NET Native AOT produce one native executable per operating system and CPU. The runtime doesn’t vanish: the executable still contains the language runtime, garbage collector included. What goes is the dynamic part. Native Image works under a closed-world assumption, so all code that can run must be known at build time, and Native AOT apps can’t load assemblies at run time or generate code with System.Reflection.Emit.

In code · bash

# Add.java
#   public class Add {
#       static int add(int a, int b) { return a + b; }
#       public static void main(String[] args) {
#           long sum = 0;
#           for (int i = 0; i < 1_000_000; i++) sum += add(i, 1);
#           System.out.println(sum);
#       }
#   }

javac Add.java      # writes Add.class: bytecode for the JVM, not for your CPU
javap -c Add        # disassemble it; add() is four stack-machine instructions
#   static int add(int, int);
#     Code:
#        0: iload_0       // push argument a onto the operand stack
#        1: iload_1       // push argument b
#        2: iadd          // pop both, push a + b
#        3: ireturn       // return the value on top of the stack

java Add            # prints 500000500000

# watch HotSpot compile add() while main() runs (JDK 21; times and ids vary)
java -XX:+PrintCompilation Add | grep 'Add::add'
#   30    6       3       Add::add (4 bytes)                      <- level 3: client compiler, code that records a profile
#   30    7       4       Add::add (4 bytes)                      <- level 4: server compiler, optimised with that profile
#   30    6       3       Add::add (4 bytes)   made not entrant   <- the level-3 version is retired

Often confused with

Where it came from1986–2021

Compiling to an abstract machine is older than Java: the JVM specification itself names UCSD Pascal’s P-code machine as the best-known example. Java made the idea mainstream. Sun designed the platform for networked consumer devices, where “compiled code had to survive transport across networks, operate on any client, and assure the client that it was safe to run.” JDK 1.0 shipped on 23 January 1996 with the promise that programmers “can write a program once, and it will run on any machine supplying a Java run-time environment.”

Early JVMs were slow, and the fix came from Smalltalk research. In 1997 Sun bought Animorphic, whose Strongtalk virtual machine descended from the Self project, and on 27 April 1999 released the HotSpot performance engine, which compiled only the methods that ran often; Sun claimed it doubled the speed of Java 2. Java SE 7 added tiered compilation, and Sun released HotSpot under the GPL on 13 November 2006. JSR 292 (final in July 2011) added an invokedynamic instruction so that dynamically typed languages could run efficiently on the JVM too.

Microsoft licensed Java in 1996, was sued for extending it, and built its own runtime. Code-named Lightning and later called .NET, it had a bytecode, garbage collection and JIT compilation, and was meant from the start to host many languages: “Project 7” set out to bring seven commercial and seven academic languages to it at launch. Ecma published its Common Language Infrastructure as ECMA-335 in December 2001, C# 1.0 shipped in January 2002, and in November 2014 Microsoft announced that .NET Core, runtime included, would be open source.

Ericsson took a different path for telephone switches. Erlang began in 1986 as a Prolog interpreter; in 1989 Joe Armstrong’s JAM (Joe’s Abstract Machine) ran it about 70 times faster, and Bogumil Hausman’s BEAM, which could also compile to C, became the machine Erlang runs on today. Both gave each process its own heap. Erlang became open source in December 1998, and BEAM got a JIT compiler, BeamAsm, in Erlang/OTP 24 in May 2021.

Why it’s built this way

Ship an abstract instruction set, not machine code
Java’s code had to be downloaded and run safely on any client. A portable format with strict structural constraints let the receiving machine check the code before running it, whatever its CPU.
Interpret first, compile what’s hot
Compiling everything up front delays start-up, and compiling without knowing the workload optimises the wrong things. HotSpot interprets, profiles, and spends compiler time only on methods that run often; tiered compilation then made start-up faster while keeping peak speed.
One runtime for many languages
Microsoft wanted .NET to be a multi-language platform rather than a fixed set of its own languages, so the CLR defines a common type system that every compiler targets. A class written in one language can be subclassed and called from another.
A separate heap for every process (BEAM)
JAM and BEAM gave each Erlang process its own heap, so one process can be collected without stopping the others. An experimental single shared heap (VEE) made message passing much faster but gave no overall gain, probably because it lowered the cache hit rate.

Timeline

  1. 1986

    Erlang work begins at the Ericsson Computer Science Laboratory

  2. 1996

    JDK 1.0 ships on 23 January

  3. 1999

    Sun releases the HotSpot JIT engine in April

  4. 2002

    C# 1.0 and the .NET Framework’s CLR ship in January

  5. 2006

    Sun releases HotSpot under the GPL on 13 November

  6. 2014

    Microsoft announces open-source .NET Core in November

  7. 2021

    Erlang/OTP 24 adds the BeamAsm JIT in May

Sources28

Improve this page on GitHub