JVM Architecture

The JVM's overall architecture — class loader subsystem, runtime data areas, execution engine, and native interface — is what turns portable bytecode into running behavior on any given machine.

The Java Virtual Machine (JVM) is the abstract machine specified by the Java Virtual Machine Specification that executes Java bytecode. Real JVM implementations (HotSpot, the reference implementation shipped by Oracle/OpenJDK, being the most common) organize themselves around a small set of cooperating subsystems: a class loader subsystem that brings bytecode into the JVM, runtime data areas that hold a running program's state, an execution engine that actually runs the bytecode, and a native interface for calling into and from native (non-Java) code. This topic gives the overall map; several of these subsystems (class loaders, runtime data areas, bytecode) are covered in much greater depth in their own dedicated topics.

The JVM as a Specification vs. an Implementation

It's important to separate what the JVM Specification actually mandates from what a particular JVM implementation happens to do. The specification defines bytecode semantics, class file format, and the general categories of runtime data areas a JVM must provide — but it deliberately leaves exact memory layout, garbage collection algorithm, JIT compilation strategy, and many performance details unspecified, as implementation choices. HotSpot, Eclipse OpenJ9, and other JVMs all conform to the same specification while differing substantially in these implementation details.

Throughout this topic, statements about 'exact' internal structure are described as typical of a modern HotSpot JVM, not as guarantees the specification makes for every conforming JVM.

High-Level Architecture Overview

At a high level, a JVM's major subsystems and how they cooperate:

loads, links, initializes.class bytecode method area, heap, JVM stacks, PC registers, native stacks Class Loader Subsystem interpreter, JIT compiler, garbage collector Runtime Data Areas bridges to/from native libraries

A typical high-level flow: classes are loaded and linked, populate the runtime data areas, and are then executed by the execution engine, which can call out through JNI to native code.

Class Loader Subsystem

This subsystem is responsible for loading (locating and reading bytecode into a Class object), linking (verification of bytecode correctness, preparation of static fields with default values, and resolution of symbolic references to concrete ones), and initialization (running static initializers and static field assignments, in source order, the first time a class is actively used). It follows the parent-delegation model across a bootstrap/platform/application loader hierarchy, and supports custom loaders — the full detail is in the dedicated Class Loaders topic.

Loading        -> find bytecode, construct a Class object in memory
Linking:
  Verification  -> bytecode is checked for structural/type correctness
  Preparation   -> static fields get default values (0, null, false)
  Resolution    -> symbolic references resolved to direct references
Initialization  -> static initializers and static field assignments run

Runtime Data Areas

Once loaded, a running JVM needs memory structures to hold program state while it executes. The specification describes several categories: the method area (per-JVM, class-level structures like the runtime constant pool, field/method data), the heap (per-JVM, shared, where objects are allocated), JVM stacks (per-thread, holding frames of local variables and the operand stack for method invocations), the program counter register (per-thread, tracking the currently executing instruction), and native method stacks (per-thread, supporting native/JNI code). The full detail — including how HotSpot maps these onto concrete structures like the metaspace — is in the dedicated JVM Runtime Data Areas topic.

The Execution Engine

The execution engine is what actually runs bytecode instructions. It comprises (in a typical modern JVM) an interpreter, a JIT (Just-In-Time) compiler, and the garbage collector, which, while conceptually a memory-management concern rather than execution per se, is usually grouped here because of how tightly it's integrated with allocation and execution in practice.

Interpreter

The interpreter reads bytecode instructions one at a time and executes them directly, without producing native machine code first. It starts running a method's bytecode immediately, with minimal startup overhead, but each execution re-does the work of decoding and dispatching each instruction — slower per-execution than running equivalent native machine code directly.

JIT Compiler

A typical modern HotSpot JVM starts by interpreting bytecode, then profiles which methods are 'hot' (executed frequently), and compiles those specific methods to native machine code at run time via a Just-In-Time (JIT) compiler — trading upfront compilation cost for much faster execution of code that actually matters for overall performance. HotSpot specifically uses a tiered compilation strategy: a fast, lightly optimizing C1 compiler kicks in early for quick wins, and a slower, more aggressively optimizing C2 compiler recompiles the hottest methods later with deeper optimizations (inlining, loop unrolling, escape analysis) once enough profiling data exists to justify the cost.

This 'HotSpot' tiered-compilation strategy is exactly where the HotSpot JVM's name comes from — it looks for the 'hot spots' in a running program to optimize. This is a JVM-implementation detail, not something the JVM Specification mandates every conforming JVM to do the same way.

Garbage Collector

The garbage collector reclaims heap memory occupied by objects that have become unreachable, removing the need for manual memory deallocation. Exactly which algorithm runs (Serial, Parallel, G1, ZGC, Shenandoah, and others exist as selectable/default collectors depending on JVM version and configuration), how it partitions the heap (e.g. young/old generations in many collectors), and its exact pause-time characteristics are all JVM-implementation and configuration details — the specification only requires that unreachable objects eventually become eligible for reclamation, not any particular algorithm or timing.

Native Method Interface (JNI)

The Java Native Interface lets Java code call into native libraries (written in C/C++, for instance) and lets native code call back into the JVM — used for performance-critical operations, hardware/OS access the JVM doesn't otherwise expose, or integrating with existing native codebases. Methods declared native in Java have no Java-level body; their implementation lives in a native library, loaded via System.loadLibrary.

This declares a native method with no body — the JVM's native interface bridges the call to a matching function inside the loaded native library at run time.

public class NativeMath {
    static {
        System.loadLibrary("nativemath"); // loads a native .so/.dll/.dylib
    }

    public native int add(int a, int b); // implemented in native code, not Java
}

Native Method Libraries

The native method libraries themselves — the actual compiled .so/.dll/.dylib files a program's native methods bind to — are typically counted as a distinct architectural piece from JNI (the bridging mechanism) itself: JNI is the interface/protocol, while the libraries are the concrete native code executing on the other side of it.

Putting It Together: A Program's Journey

End to end: javac compiles source to bytecode; java starts a JVM instance; the class loader subsystem loads, links, and initializes classes as they're needed; the runtime data areas hold the executing program's live state (objects on the heap, frames on the stacks); the execution engine runs the bytecode, starting interpreted and JIT-compiling hot paths over time; the garbage collector reclaims unreachable heap objects in the background; and, if the program uses native methods, JNI bridges calls out to (and callbacks back from) native libraries.

Key Takeaways

A JVM's architecture is a specification, conformed to by different implementations (HotSpot, OpenJ9, ...) that can differ in memory layout, GC algorithm, and JIT strategy — always distinguish specification guarantees from implementation details.

The class loader subsystem loads, links (verifies, prepares, resolves), and initializes classes on demand.

Runtime data areas hold live program state: a shared heap and method area, plus per-thread JVM stacks, PC registers, and native stacks.

The execution engine combines an interpreter (fast start, slower per-execution) with a JIT compiler (profiles and compiles hot methods to native code) and the garbage collector.

JNI bridges Java code to native libraries for native methods, letting Java call out to (and be called back from) non-Java code.

Describing a specific memory layout or GC algorithm as 'the way the JVM works,' unqualified.

Describe it as typical of a specific implementation (e.g. 'a typical modern HotSpot JVM'), since the JVM Specification leaves these as implementation choices.

Presenting an implementation detail as a language/specification guarantee misleads readers about what's actually portable across different JVMs.

Claiming Java is 'purely interpreted' because it runs on a virtual machine.

Explain that a typical modern JVM starts by interpreting bytecode and then JIT-compiles frequently executed ('hot') methods to native machine code.

Ignoring JIT compilation misrepresents where much of Java's runtime performance actually comes from.

Treating the garbage collector as guaranteeing deterministic, immediate reclamation the instant an object becomes unreachable.

State only that an unreachable object becomes eligible for collection; the specification does not guarantee when, or even that, collection happens by any particular time.

Collection timing and the specific algorithm are non-deterministic, implementation-dependent aspects of the JVM, not language guarantees.

Name the four major subsystems typically described in JVM architecture.

The class loader subsystem, the runtime data areas, the execution engine, and the native method interface (JNI), often alongside the native method libraries it bridges to.

What is the difference between what the JVM Specification mandates and what a specific JVM implementation like HotSpot does?

The specification defines bytecode semantics, class file format, and general categories of runtime data areas; it leaves exact memory layout, GC algorithm, and JIT strategy as implementation choices. HotSpot's specific behaviors in those areas are implementation details, not guaranteed by every conforming JVM.

What are the three phases of linking in class loading?

Verification (checking bytecode correctness), preparation (giving static fields default values), and resolution (converting symbolic references to direct references).

Why does a typical modern JVM use both an interpreter and a JIT compiler rather than just one?

The interpreter starts executing bytecode immediately with minimal startup cost, while the JIT compiler takes time to compile methods to native code but produces much faster execution for 'hot' (frequently run) methods — combining them balances fast startup against strong steady-state performance.

Where does the name 'HotSpot' (as in the HotSpot JVM) come from?

From its strategy of profiling a running program to find its 'hot spots' — frequently executed methods — and focusing JIT compilation effort there rather than compiling everything upfront.

What does the JVM Specification actually guarantee about garbage collection timing?

Only that an object becomes eligible for collection once it's unreachable; it does not guarantee when collection happens, or mandate any particular algorithm — those are implementation/configuration details.

What is the purpose of the Java Native Interface (JNI)?

It bridges Java code to native (non-Java) libraries, letting Java call native methods implemented in languages like C/C++ and letting native code call back into the JVM.

Write a Java class that declares a native method and loads its backing native library in a static initializer.

public class Crc32Native { static { System.loadLibrary("crc32native"); } public native long checksum(byte[] data); }

The static initializer block runs once, when the class is first initialized, loading the native library (crc32native.so/.dll/.dylib depending on platform) so the JVM's native interface can resolve the native checksum method to its native implementation before it's ever called.

Does 'Loud initialized' print before 'after reference'? Why or why not?

No, it does not print at all in this snippet. Merely referencing Loud.class does not count as active use that triggers initialization — Loud is loaded (and its Class object exists) but its static initializer runs only on true active use, such as constructing an instance, accessing a static field, or calling a static method, none of which happen here.

Describe, step by step, what a typical JVM does from java MyApp on the command line to MyApp's main method actually running.

1) The JVM starts and the bootstrap/platform/application class loaders are set up. 2) The application class loader locates MyApp.class and the class loading subsystem loads, links (verifies, prepares, resolves), and initializes it and its dependencies as needed. 3) The runtime data areas are set up (heap, method area, and a JVM stack for the main thread). 4) The execution engine begins interpreting MyApp's main method bytecode, JIT-compiling any methods that become hot, while the garbage collector manages heap memory in the background.

class Loud {
    static { System.out.println("Loud initialized"); }
    static int value = 42;
}

class Runner {
    public static void main(String[] args) {
        System.out.println("start");
        Class<?> c = Loud.class; // reference the Class object only
        System.out.println("after reference");
    }
}