The Java Virtual Machine (JVM) is the runtime that executes Java bytecode. Understanding its architecture helps with debugging, performance tuning, and writing efficient code.

JVM Structure

  ┌─────────────────────────────────────────┐
│              Class Loader Subsystem      │
│  (Loading → Linking → Initialization)   │
├─────────────────────────────────────────┤
│              Runtime Data Areas            │
│  ┌─────────┐ ┌──────┐ ┌───────────────┐ │
│  │  Heap   │ │ Stack│ │  Metaspace    │ │
│  │ (shared)│ │(thread│ │  (class meta) │ │
│  └─────────┘ └──────┘ └───────────────┘ │
│  ┌──────────────┐ ┌──────────────────┐  │
│  │  PC Register │ │ Native Method Stack│  │
│  └──────────────┘ └──────────────────┘  │
├─────────────────────────────────────────┤
│              Execution Engine              │
│  ┌──────────┐ ┌─────┐ ┌──────────────┐  │
│  │Interpreter│ │ JIT │ │  Garbage     │  │
│  │          │ │Compiler│ │  Collector  │  │
│  └──────────┘ └─────┘ └──────────────┘  │
├─────────────────────────────────────────┤
│         Native Method Interface (JNI)     │
└─────────────────────────────────────────┘
  

Class Loader Subsystem

Loads .class files into memory:

  1. Loading — Find bytecode and create a Class object
  2. Linking — Verify, prepare, and resolve references
  3. Initialization — Execute static initializers

See Class Loading for details.

Runtime Data Areas

Heap (Shared)

Stores all object instances and arrays. Shared among all threads. Divided into:

  • Young Generation — Eden + Survivor spaces (new objects)
  • Old Generation — Long-lived objects

Configured with -Xms (initial) and -Xmx (maximum) heap size.

Stack (Per Thread)

Each thread has its own stack with stack frames — one per method call. Stores:

  • Local variables
  • Operand stack
  • Reference to the constant pool

Stack overflow: StackOverflowError. Default size: -Xss (typically 1 MB).

Metaspace (Since Java 8)

Replaced PermGen. Stores class metadata — class definitions, method data, constant pools. Grows dynamically, limited by available native memory (-XX:MaxMetaspaceSize).

PC Register (Per Thread)

Points to the current instruction being executed.

Native Method Stack

Supports native (non-Java) method calls via JNI.

Execution Engine

Interpreter

Executes bytecode instructions one by one. Slow but starts immediately.

JIT Compiler

Just-In-Time compiler translates hot bytecode to native machine code for faster execution. HotSpot identifies “hot spots” through profiling.

Common JIT compilers: C1 (client, fast compilation), C2 (server, aggressive optimization).

Garbage Collector

Automatically reclaims memory from unreachable objects. See Garbage Collection.

Execution Process

  Source (.java) → javac → Bytecode (.class) → ClassLoader → JVM
                                                    ↓
                                          Execution Engine
                                          (Interpreter + JIT)
                                                    ↓
                                          Native Machine Code
  

JVM Implementations

JVM Vendor
HotSpot Oracle/OpenJDK (default)
GraalVM Oracle — polyglot, native image
OpenJ9 IBM/Eclipse — lower memory footprint
Azul Zing Azul — low-latency GC

Best Practices

  • Understand heap vs stack — objects on heap, references and primitives on stack
  • Monitor metaspace in applications with dynamic class loading
  • Use -Xmx to set appropriate heap limits in production
  • Profile before tuning — understand which runtime area is the bottleneck