Skip to main content

Welcome to a Grasp lesson

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

Lesson 1

JVM Architecture Explained

Recommended resources

Hello! Welcome to your first lesson in the Core Java course.

Given your extensive background in test automation with Java, you've likely spent countless hours writing and running Java code. This course is designed to take you "under the hood" to explore the mechanics of how that code actually works. Your goal of deepening this understanding for technical interviews is a perfect fit for our first module on JVM internals. These concepts are frequently used by interviewers to distinguish developers who merely use the language from those who truly understand it.

Today, we'll start by building a mental model of the Java Virtual Machine (JVM). We will address the learning outcome of describing the JVM's architecture by breaking it down into its three fundamental components: the Class Loader Subsystem, the Runtime Data Areas, and the Execution Engine. Think of this as learning the high-level schematic of an engine you've been driving for years.

The Big Picture: How a Java Program Runs

Before we dissect the components, let's look at the entire process from a bird's-eye view. You write a .java file, the javac compiler transforms it into a platform-independent .class file containing bytecode, and then the JVM takes over. But what does "the JVM takes over" actually mean?

To get a clear narrative of this process, please read the following short post. It outlines the entire lifecycle from compilation to termination.

30 Advanced Java Interview Questions for Product Companies | Java By Madhu Vundavalli posted on the topic | LinkedIn

This post by Madhu Vundavalli provides a concise, step-by-step summary of the Java execution flow. It connects the key architectural components we'll be discussing today into a single, coherent process.

Focus on the sequence of events described in the main body of the post, starting from the six steps of program execution. This will give you a solid framework for the rest of the lesson.

As you can see, the JVM isn't a single monolithic entity. It’s a sophisticated system with specialized components working together. Let's examine this system using a common architectural diagram.

This diagram shows the three main components of the JVM: the Class Loader Subsystem, the Runtime Data Area, and the Execution Engine. We will explore each of these in detail.

Now, let's break down each of these components.


1. The Class Loader Subsystem: The Gatekeeper

The first step in running any Java application is loading the necessary classes into the JVM. This is the job of the Class Loader Subsystem. It's responsible for finding the bytecode (in .class files or JARs), loading it into memory, and preparing it for execution. This process happens in three distinct phases: Loading, Linking, and Initialization.

The following video provides an excellent visual walkthrough of these phases and the different types of class loaders involved.

JVM Architecture ✅ : Understanding the Heart of Java ❤️

This video from the CodeSnippet channel clearly explains the role and activities of the Class Loader Subsystem.

First, watch the segment covering the three main activities: Loading, Linking, and Initialization. Pay close attention to the sub-steps of Linking (Verify, Prepare, Resolve), as these are common sources of interview questions. Next, watch the explanation of the three built-in class loaders: Bootstrap, Extension, and Application. Note the hierarchical relationship between them.

Let's recap the key points from the video, which are crucial for interviews:

  • Loading: This phase finds and reads the .class file and stores its binary data in the Method Area. It also creates a Class object on the heap to represent this type.
  • Linking: This is a multi-step process to integrate the new class into the JVM's runtime state.
    • Verification: The JVM acts as a security guard, ensuring the bytecode is valid, safe, and conforms to the JVM specification. This prevents malicious or malformed code from corrupting the runtime. If verification fails, a VerifyError is thrown.
    • Preparation: Memory is allocated for class-level (i.e., static) variables, and they are initialized with their default values (e.g., 0 for int, false for boolean, null for objects).
    • Resolution: Symbolic references within the class (e.g., a reference to another class by name) are replaced with direct, concrete memory references.
  • Initialization: This is the final step. The static variables are assigned their actual initial values from your code, and static blocks are executed. This phase is triggered only when the class is actively used for the first time (e.g., when you instantiate it or call a static method).

The Class Loader hierarchy follows a delegation model. When a class needs to be loaded, the Application Class Loader first delegates the request to the Extension Class Loader, which in turn delegates to the Bootstrap Class Loader. Only if the parent loader cannot find the class does the child loader attempt to load it itself. This prevents core Java classes from being spoofed by user-provided code and ensures that a class is loaded only once.


2. Runtime Data Areas: The JVM's Memory

Once classes are loaded, where does everything go? The JVM organizes memory into several Runtime Data Areas, each with a specific purpose. Some are shared by all threads in the JVM, while others are private to each individual thread. This distinction is fundamental to understanding concurrency in Java.

A more detailed view of the JVM architecture, highlighting the different Runtime Data Areas and their relationship with other components.

Let's watch a video that explains these memory areas, and then we'll analyze their properties.

JVM Architecture ✅ : Understanding the Heart of Java ❤️

The same CodeSnippet video also provides a clear explanation of the five main memory areas.

Please watch the segment from the start of the JVM memory section up to the end of the Native Method Stacks explanation.

Here is a breakdown of the Runtime Data Areas, focused on the details you'll need for interviews:

Area Shared/Per-Thread Purpose Key Error
Method Area Shared (1 per JVM) Stores class-level data: metadata, method code, static variables, constant pool. OutOfMemoryError: Metaspace
Heap Area Shared (1 per JVM) Stores all objects and arrays instantiated with the new keyword. OutOfMemoryError: Java heap space
Stack Area Per-Thread Stores stack frames for each method call. Each frame contains local variables. StackOverflowError
PC Registers Per-Thread Holds the address of the currently executing JVM instruction for that thread. N/A
Native Method Stack Per-Thread Stores information for native (non-Java, e.g., C/C++) method calls. (Varies by OS/native code)

Key Distinctions:

  • Heap vs. Stack: This is a classic interview question. The Heap is for all objects (new MyObject()), while the Stack is for method execution frames and local variables (including references to objects on the Heap). The Heap is managed by the Garbage Collector; the Stack's memory is automatically reclaimed as methods return.
  • Shared vs. Per-Thread: Because the Heap and Method Area are shared across all threads, they are the source of potential concurrency issues. Access to data in these areas must be properly synchronized. In contrast, each thread's Stack, PC Register, and Native Method Stack are private, making them inherently thread-safe.

3. The Execution Engine: The Workhorse

With classes loaded and memory allocated, something has to actually run the bytecode. That is the role of the Execution Engine.

JVM Architecture ✅ : Understanding the Heart of Java ❤️

Let's return to the CodeSnippet video one last time to see how the Execution Engine works.

Watch the segment that explains the components of the Execution Engine.

The Execution Engine has several key components:

  • Interpreter: The interpreter reads bytecode instructions one by one and executes them. This is simple and portable, but can be slow if a method is called repeatedly, as it must be interpreted each time.
  • Just-In-Time (JIT) Compiler: To solve the interpreter's performance bottleneck, the JVM includes a JIT compiler. The Execution Engine profiles the code as it runs to identify "hot spots"—frequently executed methods and loops. The JIT compiler then compiles this hot bytecode directly into highly optimized native machine code for the host CPU. Subsequent calls to that method will execute the fast native code instead of being interpreted, leading to significant performance gains in long-running applications.
  • Garbage Collector (GC): The GC is a part of the Execution Engine that automates memory management. It runs in the background, identifying and reclaiming memory from objects in the Heap that are no longer referenced by the application. We will cover this process in much greater detail in a future lesson.

Finally, the Java Native Interface (JNI) is a framework that allows Java code running in the JVM to call, and be called by, native applications and libraries written in other languages like C and C++.


Conclusion

In this lesson, we've constructed a foundational model of the JVM architecture. You now have a map of how Java executes your code, from the moment a .class file is presented to the JVM until the final instruction is run.

Here are the key takeaways:

  • The JVM is composed of three main parts: the Class Loader Subsystem, Runtime Data Areas, and the Execution Engine.
  • The Class Loader dynamically loads, links, and initializes classes using a hierarchical delegation model.
  • The Runtime Data Areas organize memory into thread-safe (Stack, PC Registers) and shared (Heap, Method Area) regions, each with a distinct purpose. The distinction between Heap and Stack is particularly important.
  • The Execution Engine uses a combination of an Interpreter for portability and a JIT Compiler for performance to execute bytecode.

This high-level understanding is the bedrock for troubleshooting complex issues like memory leaks, performance tuning, and concurrency bugs.

In our next lesson, we will zoom in on the most dynamic of the Runtime Data Areas. We'll explore the Java memory model in more detail, focusing on the relationship between the Heap, Stack, and the modern Metaspace, which will set the stage for understanding the magic of Garbage Collection.