Skip to content

JVM Internals

The JVM executes Java bytecode. Key components: ClassLoader (loads classes), Runtime Data Areas (heap, stack, method area, PC register), Execution Engine (interpreter + JIT compiler). The JIT compiler optimizes hot code paths at runtime. Understanding JVM internals helps in performance tuning and debugging.


JVM Architecture Overview

Three main subsystems: ClassLoader (loads, links, initializes classes using parent-delegation), Runtime Data Areas (heap for objects, stack per thread, method area for class metadata), and Execution Engine (interpreter for cold code, JIT compiler for hot paths, GC for memory). Code flows: .javajavac.class (bytecode) → JVM interprets/JIT-compiles → native execution.

Deep Dive: Architecture Diagram
┌──────────────────────────────────────────────────┐
│              Class Loader Subsystem              │
│    (Bootstrap → Extension → Application)         │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│           Runtime Data Areas                     │
│  ┌──────────┐  ┌────────┐  ┌─────────────────┐  │
│  │  Heap    │  │ Method │  │ PC Register     │  │
│  │(Shared)  │  │  Area  │  │ (Per Thread)    │  │
│  └──────────┘  │(Shared)│  └─────────────────┘  │
│                └────────┘                        │
│  ┌──────────────────────┐  ┌─────────────────┐  │
│  │  JVM Stack           │  │ Native Method   │  │
│  │  (Per Thread)        │  │ Stack           │  │
│  └──────────────────────┘  └─────────────────┘  │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│           Execution Engine                       │
│   ┌──────────┐  ┌───────────┐  ┌─────────────┐  │
│   │Interpreter│  │JIT Compiler│  │GC           │  │
│   └──────────┘  └───────────┘  └─────────────┘  │
└──────────────────────────────────────────────────┘

ClassLoader Subsystem

ClassLoaders load .class files into the JVM using parent-delegation: child delegates to parent first, ensuring core classes can't be replaced. Three built-in loaders: Bootstrap (core Java — java.lang, java.util), Extension/Platform (extensions), Application (classpath). Loading phases: LoadLink (verify + prepare + resolve) → Initialize.

Deep Dive: Delegation Model & Custom ClassLoader
// Custom ClassLoader
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes = loadClassData(name);
        return defineClass(name, bytes, 0, bytes.length);
    }
}

Loading phases:

  1. Loading — read .class file, create Class object
  2. Linking:
    • Verification — bytecode validity
    • Preparation — allocate memory for static variables
    • Resolution — replace symbolic references with direct references
  3. Initialization — execute static initializers

Runtime Data Areas

Heap (shared) — stores objects/arrays, GC-managed, -Xms/-Xmx. Method Area / Metaspace (shared) — class metadata, static vars, constant pool (replaced PermGen in Java 8). JVM Stack (per thread) — method frames, local variables, -Xss. PC Register (per thread) — current instruction address. Native Method Stack (per thread) — for JNI calls.

Deep Dive: Heap Structure
┌────────────────────────────────────────────┐
│ Young Generation                           │
│  ┌─────────┐  ┌──────────┐  ┌──────────┐  │
│  │  Eden   │  │Survivor 0│  │Survivor 1│  │
│  └─────────┘  └──────────┘  └──────────┘  │
├────────────────────────────────────────────┤
│ Old Generation (Tenured)                   │
└────────────────────────────────────────────┘

Key sizes:

Area Flag Default Error
Heap initial -Xms JVM-dependent
Heap max -Xmx --- OutOfMemoryError: Java heap space
Metaspace -XX:MaxMetaspaceSize Unlimited OutOfMemoryError: Metaspace
Stack -Xss ~512KB-1MB StackOverflowError

Execution Engine: JIT Compiler

The interpreter executes bytecode line-by-line (slow but fast startup). The JIT compiler identifies "hot" code paths (frequently called methods, loops) and compiles them to native machine code for much faster execution. Java uses tiered compilation (default since Java 8): C1 compiler (fast, less optimized) → C2 compiler (slower compilation, aggressive optimization).

Deep Dive: How JIT Works
  1. Interpreter starts executing bytecode
  2. JVM profiles execution (counts method calls, loop iterations)
  3. When threshold is reached, JIT compiles to native code
  4. Future calls execute compiled code (much faster)

Compiler tiers:

  • C1 (Client) — fast compilation, basic optimizations
  • C2 (Server) — slower compilation, aggressive optimizations (inlining, escape analysis, loop unrolling)
  • Tiered (default) — uses both: C1 first, C2 for hot methods

Flags:

-Xint                        # Interpreter only (no JIT)
-Xcomp                       # Compile all methods (no interpreter)
-XX:+TieredCompilation       # Tiered compilation (default)


Garbage Collection Algorithms

Key GC algorithms: Serial (single-threaded, small heaps), Parallel (multi-threaded, throughput-focused, Java 8 default), G1 (region-based, predictable pauses, Java 9+ default), ZGC (ultra-low latency < 10ms, Java 11+). Choose based on heap size and latency requirements.

Deep Dive: GC Algorithms Comparison
GC Flag Threads Pauses Best For
Serial -XX:+UseSerialGC Single Long STW Small heaps (<100MB)
Parallel -XX:+UseParallelGC Multi STW Throughput (Java 8 default)
G1 -XX:+UseG1GC Multi Predictable General (Java 9+ default)
ZGC -XX:+UseZGC Concurrent <10ms Low latency (Java 11+)
Shenandoah -XX:+UseShenandoahGC Concurrent Low Alternative to ZGC

GC process:

  • Minor GC — cleans Young Generation (Eden + Survivors)
  • Major GC — cleans Old Generation
  • Full GC — cleans entire heap (expensive, stop-the-world)
Deep Dive: GC Tuning Flags
-Xms2g -Xmx4g                  # Heap size
-XX:NewRatio=2                 # Old:Young ratio (default 2:1)
-XX:SurvivorRatio=8            # Eden:Survivor ratio (default 8:1:1)
-XX:MaxTenuringThreshold=15    # GC cycles before promotion to Old Gen
-XX:MaxGCPauseMillis=200       # Target pause time (G1)
-Xlog:gc*                      # GC logs (Java 9+)

Bytecode Basics

Java source compiles to bytecode (.class files) — platform-independent instructions executed by the JVM. The JVM is a stack-based virtual machine. You can inspect bytecode with javap -c ClassName. Bytecode is verified before execution for security (prevents buffer overflows, invalid casts).

Deep Dive: Bytecode Example
public int add(int a, int b) {
    return a + b;
}

Bytecode (javap -c ClassName):

0: iload_1        // Load 'a' onto operand stack
1: iload_2        // Load 'b' onto operand stack
2: iadd           // Add top two values
3: ireturn        // Return result

Key insights:

  • JVM is stack-based (vs register-based like CPU)
  • Bytecode is verified before execution (security)
  • Platform-independent → "Write Once, Run Anywhere"

Common Interview Questions

Common Interview Questions
  • Explain the JVM architecture.
  • What are the different memory areas in the JVM?
  • Explain the ClassLoader subsystem and delegation model.
  • What is the JIT compiler? How does it improve performance?
  • What is the difference between C1 and C2 compilers?
  • What garbage collection algorithms do you know?
  • Which GC would you choose for low latency applications?
  • What is Metaspace? How is it different from PermGen?
  • What is bytecode? How does the JVM execute it?
  • How do you troubleshoot high CPU usage in a Java application?