Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

JVM Architecture Explained

Recommended resources

Hello! Welcome back to your course on interview preparation for Core Java, Spring Boot, and Microservices.

In our last session, we clarified the roles of the JDK, JRE, and JVM, establishing the JVM as the core engine that executes Java bytecode. You learned that the JDK is for development, the JRE is for running applications, and the JVM is the platform-specific component that makes "Write Once, Run Anywhere" a reality.

Today, we're going to open up that JVM "black box." This lesson directly addresses the learning outcome: Describe the JVM's runtime data areas, class loading mechanism, and execution engine. Understanding this internal architecture is a key differentiator in senior-level interviews. It demonstrates a fundamental grasp of the platform that goes beyond simply writing code and is crucial for diagnosing complex runtime issues like memory leaks or performance bottlenecks.

The Three Pillars of the JVM

At a high level, the JVM's architecture is composed of three main subsystems that work together to run your compiled Java code.

  1. Class Loader Subsystem: Responsible for finding, loading, and preparing your class files.
  2. Runtime Data Areas: The memory model of the JVM, where all data is stored during execution.
  3. Execution Engine: Responsible for actually executing the bytecode.

This diagram provides a great mental map of how these components are organized. We will refer back to it as we explore each part in detail.

JVM Architecture Diagram
This diagram illustrates the complete JVM architecture. It shows how a .class file is processed by the Class Loader Subsystem, how data is organized in the Runtime Data Areas (like the Heap and Stack), and how the Execution Engine (with its Interpreter and JIT Compiler) runs the code.

Let's dive into each of these subsystems.


1. Class Loader Subsystem

Before the JVM can run any code, it must first load the necessary classes. This is the job of the Class Loader Subsystem, which performs this task in three phases: Loading, Linking, and Initialization.

To get a comprehensive overview of this process, let's watch a segment from the "JVM architecture" tutorial.

JVM ( java virtual machine) architecture - tutorial

This section of the video breaks down the entire class loading process, from the three types of class loaders to the detailed steps of linking and initialization.

Watch the video from 04:00 to 12:35. Pay close attention to: The delegation model involving the Bootstrap, Extension (now Platform), and Application class loaders. The three phases: Loading, Linking (Verify, Prepare, Resolve), and Initialization. The distinction between what happens during the 'Prepare' phase (memory allocation with default values) and the 'Initialize' phase (assignment of actual values).

Let's summarize the key points from the video:

  • Loading: This phase finds the .class file on disk (or network) and brings its bytecode into the Method Area. The JVM uses a delegation hierarchy of class loaders:

    1. Bootstrap Class Loader: Loads core Java API classes (like String, Object) from rt.jar. It's the "parent" of all others and is implemented in native code.
    2. Extension Class Loader (renamed Platform Class Loader in Java 9+): Loads classes from the JRE's extension directory.
    3. Application (or System) Class Loader: Loads the classes from your application's classpath.
  • Linking: This phase makes the class ready for execution.

    • Verification: A security step. The bytecode verifier checks that the .class file is valid and won't compromise the JVM. If it fails, you get a VerifyError.
    • Preparation: The JVM allocates memory for static variables and initializes them with their default values (e.g., 0 for int, false for boolean, null for objects).
    • Resolution: The JVM replaces symbolic references in the code (like class names) with actual direct memory references.
  • Initialization: This is the final step. The initial values specified in your code are assigned to static variables, and any static blocks are executed. This phase is triggered only when the class is first actively used (e.g., by creating an instance or calling a static method).

Test your understanding!

An interviewer asks: "What is the difference between a ClassNotFoundException and a NoClassDefFoundError?" How would you explain it based on the class loading mechanism?

Show answer

The difference lies in when the class is missing.

  • A ClassNotFoundException is thrown when you try to load a class at runtime using reflection (e.g., Class.forName()) or dynamic class loading, but the class file cannot be found on the classpath. This happens during the Loading phase when the class loader actively tries to locate the file and fails.

  • A NoClassDefFoundError is an Error that occurs when a class was successfully loaded by the class loader earlier, but is somehow unavailable during runtime when it's needed. This typically happens if an exception occurred during the Initialization phase (e.g., in a static block) of that class. The class is now in a broken state, and any subsequent attempt to use it will result in this error. It can also happen if a class was present at compile time but missing from the classpath at runtime.

In short: ClassNotFoundException = "I tried to find it and couldn't." NoClassDefFoundError = "I found it before, but now it's broken or gone when I need to use it."


2. Runtime Data Areas

Once classes are loaded, the JVM needs a place to store data and instructions. These are the Runtime Data Areas, essentially the memory of the JVM. Some areas are shared among all threads, while others are private to each thread.

The following segments of the video we started earlier explain these memory areas clearly.

JVM ( java virtual machine) architecture - tutorial

These sections of the video detail the five major runtime data areas, separating them into those shared by the JVM and those created per thread.

Watch the video from 12:35 to 22:55. Focus on: Shared areas: Method Area (Metaspace) and Heap. Note what kind of data goes into each. Per-thread areas: PC Register, Java Stack, and Native Method Stack. Understand why each thread needs its own. The cause of OutOfMemoryError (related to Heap/Metaspace) and StackOverflowError (related to the Stack).

Let's break down these areas, which are also shown in the architectural diagram at the start of the lesson.

Areas Shared by All Threads

  1. Method Area:

    • Stores per-class information such as class metadata, the runtime constant pool, field and method data, and the code for methods.
    • Crucially, static variables are also stored here.
    • Before Java 8, this was a fixed-size part of the heap called PermGen (Permanent Generation). Running out of space here caused an OutOfMemoryError: PermGen space.
    • Since Java 8, PermGen has been replaced by Metaspace, which is allocated from native memory, not the Java heap. This makes it more flexible as it can grow automatically, reducing the frequency of that specific OutOfMemoryError.
  2. Heap Area:

    • This is where all objects and arrays are allocated at runtime.
    • It is the largest memory area and is managed by the Garbage Collector.
    • When the heap runs out of space to allocate new objects, the JVM throws an OutOfMemoryError: Java heap space.

Areas Private to Each Thread

When a new thread is created, it gets its own set of these three data areas. This isolation is a key reason why local variables are inherently thread-safe.

  1. Java Stack:

    • Stores stack frames. A new frame is created for each method invocation and destroyed when the method completes.
    • Each stack frame contains:
      • Local variables for that method.
      • An operand stack (a workspace for intermediate calculations).
      • A reference to the runtime constant pool.
    • If a thread's computation requires a deeper stack than allowed, a StackOverflowError is thrown. This is common with unterminated recursive calls.
  2. PC (Program Counter) Register:

    • A very small memory area that holds the address of the JVM instruction currently being executed. Each thread has its own PC register to keep track of its own execution sequence.
  3. Native Method Stack:

    • Similar to the Java Stack, but used for native (non-Java) method invocations, such as code written in C/C++ and called via the Java Native Interface (JNI).
Test your understanding!

Consider the following code snippet:

public class MemoryExample {
    private static String staticField = "I'm static";
    private String instanceField = "I'm an instance";

    public void someMethod() {
        int localVariable = 100;
        MemoryExample anotherObject = new MemoryExample();
    }
}

If someMethod() is executed, in which runtime data areas would the following be located?

  1. The staticField variable
  2. The instanceField variable of an object
  3. The localVariable
  4. The anotherObject reference variable
  5. The actual object that anotherObject refers to
Show answer
  1. staticField: In the Method Area (or Metaspace), because it is a static variable associated with the MemoryExample class itself.
  2. instanceField: In the Heap, as part of the MemoryExample object's data.
  3. localVariable: On the Java Stack, inside the stack frame for someMethod().
  4. anotherObject reference: On the Java Stack, inside the stack frame for someMethod(). It's a local variable that holds a memory address.
  5. The actual MemoryExample object: In the Heap. The anotherObject reference on the stack points to this object in the heap.

3. Execution Engine

The Execution Engine is the component that reads the bytecode from the memory areas and executes it. It's the true "processor" of the JVM.

JVM ( java virtual machine) architecture - tutorial

This final clip from the tutorial introduces the main components of the Execution Engine.

Watch from 24:09 to 27:11. This will explain the roles of: The Interpreter. The Just-In-Time (JIT) Compiler. The Garbage Collector (GC) as a part of the engine.

The core components of the Execution Engine are:

  • Interpreter: Reads bytecode instruction by instruction, interprets it, and executes it. This is straightforward but relatively slow because the same method will be re-interpreted every time it's called.

  • Just-In-Time (JIT) Compiler: To improve performance, the JVM uses a JIT compiler. The JVM profiles the code as it runs, identifying "hotspots"—methods that are executed frequently. The JIT compiler then compiles these hotspots into highly optimized native machine code that can be executed directly by the CPU, bypassing the interpreter. This is why Java applications often "warm up" over time, getting faster after an initial period of interpretation and profiling.

  • Garbage Collector (GC): This component runs in the background to automatically find and reclaim memory from objects that are no longer referenced, freeing up space in the Heap. We will dedicate an entire future lesson to the GC, but for now, know that it's an essential part of the Execution Engine.

For a deeper perspective suited for senior-level interviews, it's useful to understand that the "Execution Engine" isn't just one thing, but a sophisticated system of interpretation and multiple levels of compilation.

JVM Anatomy 101

This video from JetBrains, 'JVM Anatomy 101', offers a more nuanced look at how code is executed, discussing different types of interpreters and compilers. This level of detail is excellent for your interview preparation goals.

Watch the segment from 17:49 to 24:30. This covers the trade-offs between interpretation and compilation, and how modern JVMs use a combination of both for optimal performance. Note the discussion of simple vs. optimizing compilers.


Conclusion

You've now taken a detailed tour inside the JVM. This knowledge is not just academic; it's the foundation for understanding performance tuning, concurrency, and memory management in Java.

Let's summarize the key takeaways:

  • JVM Architecture: Comprises the Class Loader Subsystem, Runtime Data Areas, and Execution Engine.
  • Class Loading: A three-phase process (Load, Link, Initialize) that uses a delegation model to bring classes into the JVM.
  • Runtime Data Areas: The JVM's memory is divided into the Heap (for objects), Method Area/Metaspace (for class data), and per-thread areas like the Java Stack (for local variables and method frames), PC Register, and Native Method Stack.
  • Execution Engine: Uses a combination of a slow-but-simple Interpreter and a powerful JIT Compiler to execute bytecode efficiently, while the Garbage Collector manages memory automatically.

In our next lesson, we will focus on a very common interview topic: String, StringBuilder, and StringBuffer. We will explore string immutability and the String pool, a concept that directly relates to the Heap and Method Area we discussed today.