Demystifying the JVM: Architecture, Runtime Data Areas, and Core Concepts
This article introduces the Java Virtual Machine’s overall architecture and its runtime data areas, covering class files, class loaders, execution engine components, native interfaces, and detailed explanations of the program counter, stacks, heap, method area, and constant pool, providing a solid foundation for Java developers.
Preface
Previously my blog posts were quite random, written whenever inspiration struck. While this freedom was enjoyable, it caused indecision about the next topic and resulted in a lack of systematic content for readers. Going forward I will write series‑style posts, focusing primarily on the JVM (Java Virtual Machine) and JUC (java.util.concurrent). Understanding the JVM is a basic requirement for Java developers and a frequent interview topic. This series will introduce the most important aspects of the JVM, referencing the book Deep Understanding of the Java Virtual Machine and adding my own insights.
1. Java Virtual Machine Architecture (JVM Architecture)
To study any technology effectively, one should first grasp its overall structure. Below is a diagram of the JVM architecture to help readers visualize it.
Java Virtual Machine Architecture Diagram
For beginners the diagram may look overwhelming, but the interview‑relevant parts are the runtime data area, garbage collector, memory allocation strategies, class loading mechanism, and class file structure. This article will briefly introduce each component.
1.1 Class File (Bytecode File)
Java’s “write once, run anywhere” promise relies on the virtual machine and class files. Source code is compiled into a .class file, which can be executed on any OS that has a JVM, providing platform independence. The same mechanism enables language independence: languages such as Kotlin, Scala, and Groovy also compile to class files and run on the JVM.
HelloWorld program and its compiled Class file
JVM provides platform and language independence
1.2 Class Loader Subsystem
Before a class file can be executed it must be loaded into memory by a ClassLoader. The JVM provides three built‑in loaders: Bootstrap, Extension, and Application. Custom loaders can also be added.
Class loading process
The loading process consists of three stages—loading, linking, and initialization. Linking further splits into verification, preparation, and resolution.
1.3 Java Virtual Machine Runtime Data Area
This topic will be covered in detail in the second part of the series.
1.4 Execution Engine
After bytecode is loaded into the runtime data area, the execution engine reads and runs it. It includes:
Interpreter: Converts bytecode to machine code at runtime, which makes Java an interpreted language and generally slower than compiled languages like C++.
JIT Compiler (Just‑In‑Time): Compiles frequently executed (“hot”) code into native machine code and stores it in a code cache, improving performance.
Garbage Collector: Automatically reclaims memory, a topic that will be explored in later articles.
1.5 Native Interface (JNI)
Methods marked with the native keyword have no Java body because they invoke native libraries written in C or C++. Examples include:
// Example 1: Thread.currentThread() native method
public static native Thread currentThread();
// Example 2: FileInputStream.open0() native method
private native void open0(String name) throws FileNotFoundException;1.6 Native Method Library
The native methods are implemented in C/C++ code residing in the native library.
2. Java Virtual Machine Runtime Data Area
The runtime data area is crucial because most runtime errors such as StackOverflowError and OutOfMemoryError originate here. The diagram below shows the layout: program counter, JVM stacks, native method stacks (thread‑private), and the shared heap and method area (which contains the runtime constant pool).
Java Virtual Machine Runtime Data Area
2.1 Program Counter Register
The program counter is a small memory space that points to the next bytecode instruction for each thread. It enables control‑flow features such as branching, loops, exception handling, and thread resumption. Each thread has its own independent PC, making it thread‑private. When executing native methods the PC is undefined.
2.2 JVM Stack
The JVM stack is also thread‑private and stores stack frames for each method invocation. Each frame contains a local variable table, operand stack, dynamic linking information, and a method return address. The local variable table holds primitive types, object references, and return addresses. The operand stack is used by bytecode instructions, which are stack‑based. Stack overflow triggers StackOverflowError ; inability to expand the stack triggers OutOfMemoryError .
The internal structure is illustrated below:
JVM Stack
2.2.1 Local Variable Table
Stores method parameters and local variables. For non‑static methods, index 0 holds the reference to the owning object.
2.2.2 Operand Stack
A stack‑like structure used by bytecode instructions. Example of i++ vs ++i demonstrates the order of operations on the operand stack and local variable table.
2.2.3 Dynamic Linking
Each stack frame contains a reference to the constant pool entry of the current method, supporting dynamic method resolution.
2.2.4 Method Exit
Methods can exit normally (return bytecode) or abruptly (exception). In both cases the current frame is popped, and control returns to the caller.
2.3 Native Method Stack
The native method stack serves the same purpose for native (C/C++) methods as the JVM stack does for Java bytecode. Implementations may merge the two stacks; overflow or expansion failures also raise StackOverflowError or OutOfMemoryError .
2.4 Java Heap
The heap is the largest memory region, shared by all threads, used to allocate object instances. It is managed by the garbage collector and can be of fixed or expandable size (controlled by -Xmx and -Xms ). Exhaustion of heap space results in OutOfMemoryError .
2.5 Method Area
The method area (also called “non‑heap”) stores loaded class metadata, static variables, runtime constant pool, and JIT‑compiled code. Earlier JVMs used a “Permanent Generation” (PermGen) for this purpose; since JDK 8 it has been replaced by Metaspace, which resides in native memory.
2.6 Runtime Constant Pool
The runtime constant pool is part of the method area. It holds literals and symbolic references from class files after they are loaded. When the constant pool cannot allocate more memory, an OutOfMemoryError is thrown.
Conclusion
This first article of the JVM series introduced the overall architecture and the runtime data area, giving readers a solid overview of how the JVM works. More detailed topics such as garbage collection, memory models, and performance tuning will be covered in upcoming posts.
References
Zhou Zhimin, Deep Understanding of the Java Virtual Machine: Advanced Features and Best Practices
Java Virtual Machine Specification
Java Memory Model and Runtime Data Areas
Architecture of JVM
Differences between compiled and interpreted languages
JVM Overview: Bytecode, Instructions, JIT Execution
Understanding Java Constant Pools
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITFLY8 Architecture Home
ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
