JVM Architecture
AdvancedThe JVM comprises a class loader, runtime data areas, execution engine, and native interface — understanding these is key for performance tuning.
Overview
The Java Virtual Machine is the runtime that executes Java bytecode. It consists of: the Class Loader Subsystem (loads, links, initialises classes), Runtime Data Areas (Heap, Method Area/Metaspace, Stack, PC Register, Native Method Stack), and the Execution Engine (Interpreter, JIT Compiler, Garbage Collector). Understanding JVM architecture is essential for diagnosing memory issues, performance bottlenecks, and startup problems.
Class Loader Subsystem
Class loading happens in three phases: 1. Loading — reads .class bytecode from file system, JAR, or network. 2. Linking — Verification (bytecode valid?), Preparation (static fields allocated with defaults), Resolution (symbolic references resolved to direct references). 3. Initialisation — static initializers and static field assignments run.
Three built-in loaders form a delegation hierarchy: Bootstrap (loads java.lang.*), Platform/Extension, Application (loads classpath classes).
// Inspect class loaders
Class<?> cls = String.class;
System.out.println(cls.getClassLoader()); // null = Bootstrap
Class<?> userCls = MyApp.class;
ClassLoader appLoader = userCls.getClassLoader();
System.out.println(appLoader); // AppClassLoader
System.out.println(appLoader.getParent()); // PlatformClassLoader
System.out.println(appLoader.getParent().getParent()); // null (Bootstrap)
// Force class loading
Class<?> loaded = Class.forName("com.example.SomeService");
// Triggers: Loading → Linking → Initialisation
// Lazy loading — class loaded only when first used
// JVM loads a class the first time its bytecode is needed
// Custom class loader
public class HotReloadLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadBytecodeFromDisk(name);
return defineClass(name, bytes, 0, bytes.length);
}
}Runtime Data Areas
Heap: shared across all threads; stores objects and arrays. GC manages this area. Method Area (Metaspace in Java 8+): stores class metadata, static variables, constant pool. Off-heap by default in Java 8+.
JVM Stack: per-thread; each method call creates a frame holding local variables, operand stack, and return address. PC Register: per-thread; holds current instruction address. Native Method Stack: per-thread; used for native (JNI) calls.
/*
JVM Memory Layout (Java 8+)
┌─────────────────────────────────────────┐
│ Heap │
│ ┌─────────────┐ ┌────────────────────┐│
│ │ Young Gen │ │ Old Gen (Tenured) ││
│ │ Eden|S0|S1 │ │ ││
│ └─────────────┘ └────────────────────┘│
└─────────────────────────────────────────┘
┌────────────────────┐
│ Metaspace (off-heap — class metadata) │
└────────────────────┘
Per-thread:
┌──────────┐ ┌────────────┐ ┌──────────┐
│JVM Stack │ │PC Register │ │NativeStk │
│[Frame 3] │ │0x00FA12.. │ │ │
│[Frame 2] │ └────────────┘ └──────────┘
│[Frame 1] │
└──────────┘
*/
// Stack overflow — too many nested frames
public int factorial(int n) {
return n * factorial(n - 1); // no base case → StackOverflowError
}
// OutOfMemoryError: Java heap space
List<byte[]> list = new ArrayList<>();
while (true) list.add(new byte[1024 * 1024]); // fill heap
// OutOfMemoryError: Metaspace
// Typically caused by generating too many classes at runtimeExecution Engine — Interpreter and JIT
The Execution Engine interprets bytecode or compiles it to native machine code via JIT (Just-In-Time) compilation.
C1 compiler: client compiler, fast compilation, moderate optimisation — used at startup. C2 compiler: server compiler, slow compilation, aggressive optimisation — used for hot methods. Tiered Compilation (default since Java 8): starts with C1, promotes hot methods to C2. GraalVM JIT: alternative high-performance JIT that can also do ahead-of-time (AOT) compilation.
// JIT flags useful for diagnostics
// -XX:+PrintCompilation — show methods being JIT-compiled
// -XX:+PrintInlining — show method inlining decisions
// -XX:CompileThreshold=N — compile after N invocations (default ~10000)
// -Xint — interpreter-only (no JIT, for testing)
// -XX:+TieredCompilation — tiered C1→C2 (default)
// Example: viewing JIT in action
for (int i = 0; i < 100_000; i++) {
hotMethod(i); // JIT compiles after ~10,000 invocations
}
// JIT optimisations:
// • Method inlining — eliminate call overhead for small methods
// • Dead code elim — remove unreachable code
// • Loop unrolling — unroll tight loops
// • Escape analysis — allocate objects on stack if they don't escape
// • Intrinsics — replace known methods (e.g. String.equals) with native codeInteractive Visualization
Key Points to Remember
- Class loading: Loading → Linking (Verify/Prepare/Resolve) → Initialisation.
- Bootstrap → Platform → Application class loaders form the delegation chain.
- Heap: shared object storage. Stack: per-thread method frames. Metaspace: class metadata (off-heap).
- JIT compiles hot methods to native code; Tiered Compilation uses C1 then C2.
- StackOverflowError = stack full; OutOfMemoryError = heap or Metaspace full.
Practice JVM Architecture in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat are the three phases of class loading?
What is the difference between Heap and Metaspace?
What is the parent delegation model in class loading?
What is the difference between C1 and C2 JIT compilers?
What is escape analysis and how does the JVM use it?
Ask Aria about JVM Architecture
Your personal AI tutor — ask anything about this concept