How the JVM Works: ClassLoading, Linking, and Runtime Internals

Published on
/1 mins read/...

Introduction

Java's primary promise has always been: **"Write Once, Run Anywhere"** (WORA). This cross-platform portability is made possible by the **Java Virtual Machine (JVM)**. Instead of compiling code directly into machine-specific assembly instructions, Java source code is compiled into platform-independent intermediate **bytecode**.

When you execute a Java program, the JVM loads this bytecode, links class references, manages memory regions dynamically, and compiles frequently executed paths into machine code at runtime. Let's look under the hood of the JVM architecture to see how it executes classes.

ClassLoader Parent Delegation Model

The JVM's ClassLoader subsystem brings compiled .class byte files into runtime memory. To prevent security vulnerabilities (like an application overriding core classes like java.lang.System), Java enforces the **Parent Delegation Model**.

When a request to load a class arrives, the current ClassLoader delegates the request up to its parent. Only if the parent cannot find the class does the child attempt to load it. The hierarchy consists of three standard ClassLoaders: Bootstrap ClassLoader (JDK classes), Platform ClassLoader (Extension APIs), and the Application ClassLoader (local classpath code).

ClassLoader Parent Delegation Simulator

Select a class type and trigger loading to observe the security delegation path

Bootstrap ClassLoaderCore Runtime / rt.jar
Platform ClassLoaderExtensions / ext-path
Application ClassLoaderLocal CLASSPATH
Resolution Pipeline Log
Select a class and click Load Class to trace delegation...

Dynamic Linking & Loading Phases

Once a ClassLoader locates class bytes, the JVM processes it through three distinct phases:

  • Loading: The binary data of the class is ingested and a Class object is created in the Method Area.
  • Linking: Combines class references.
    • Verify: Inspects bytecode structural safety to prevent corrupted or malicious code.
    • Prepare: Allocates memory for static fields and assigns initial default type values.
    • Resolve: Converts symbolic names/strings in the constant pool into direct memory address references.
  • Initialization: Executes static initializer blocks and assigns static fields their actual declared values.

JVM Memory Allocation Architecture

During execution, the JVM allocates memory in specific regions collectively called the **Runtime Data Areas**. These areas are divided into memory shared by all threads and memory isolated to individual threads.

Understanding where variables are allocated is crucial for managing garbage collection profiles and avoiding stack overflow or out-of-memory errors. Investigate the interactive inspector below to see how Java code declarations map to memory.

JVM Runtime Data Area Inspector

Click a memory region block below to observe allocation scope and Java code mapping

JVM Runtime Memory Layout Map

Heap Area

Shared (Per JVM)

Stores the actual data objects allocated via new keyword. Managed by the Garbage Collector.

Allocated Data
All dynamic class instances and arrays.
Java Code Context Example
User userRef = new User("Alice", 28);
Memory Mapping Mechanics:

The actual new User(...) object values (string data "Alice" and integer 28) are stored here in Heap memory.

Interactive ClassLoader Simulator

(Use the delegation simulator above to load core, platform, or app classes and trace the parent lookup path.)

Interactive JVM Memory Inspector

(Select JVM memory blocks in the inspector above to explore scopes and see how Java variable references resolve between Stack and Heap.)

JIT Compilation & Garbage Collection

To run bytecode quickly, the JVM uses an **Execution Engine** containing both an Interpreter and a **Just-In-Time (JIT) Compiler**. The Interpreter runs bytecode instructions sequentially (high start speed, low run speed).

As the code runs, the JVM tracks "hot methods" that are executed frequently. The JIT Compiler compiles these hot paths directly into native machine code, bypassing interpreter loops for subsequent calls. Combined with a generational **Garbage Collector** (GC) that sweeps unused Heap allocations automatically, the JVM delivers compiled machine speeds with managed runtime memory safety.


Original system design topics compiled from the ByteByteGo JVM architecture series.

Reactions