Java

Java Memory Model and JVM Architecture

Introduction:


Understanding the Java memory model and the JVM architecture is crucial for Java developers, as it provides insights into how Java programs execute and how memory is managed.

This knowledge is particularly important for developing, deploying, monitoring, and tuning the performance of Java applications. In this blog post, we will delve into the Java memory model and explore the various components of the JVM architecture.

JVM Architecture Overview:


The JVM (Java Virtual Machine) is an abstract computing machine that executes Java bytecode. It is the component of the Java technology responsible for its hardware- and operating system-independence. The JVM is divided into three main subsystems:

  1. Class Loader Subsystem: This subsystem is responsible for loading, linking, and initializing Java classes. It includes the Bootstrap Class Loader, Extension Class Loader, and Application Class Loader.
  2. Runtime Data Area: This area holds the memory structures required during the execution of a Java program. It includes:

    Java Memory Model
    • Method Area: This is where the bytecode of methods is stored, along with class level information such as static variables, the constant pool, the code for methods and constructors, etc.
    • Heap Area: This is where all created objects and their instance variables and arrays are stored. When an object is created using the new keyword, memory is allocated in the Heap area.
    • Stack Area: Each thread in Java has its own JVM stack created at the same time as the thread. A new frame is created each time a method is invoked and is pushed onto the stack. The frame is destroyed when the method invocation completes. So, local variables, and partial results are stored in the Stack area of memory.
    • PC (Program Counter) Registers: Each JVM thread has its own PC Register. At any point, each Java thread is executing the code of a single method, except for methods for which the JVM has inlined the code. If that method is not native, the PC register contains the address of the JVM instruction currently being executed.
    • Native Method Stack: It contains all the native methods used in the application. Note that the memory for the native method stack is allocated per thread when each thread is created. Separate stacks used for native methods (methods written in languages other than Java, such as C or C++).

  3. Execution Engine: This component executes the bytecode by utilizing various components:
    • Interpreter: Reads bytecode stream, then executes instructions.
    • JIT Compiler: Compiles bytecode into native machine code for faster execution.
    • Garbage Collector: Automatically manages the allocation and deallocation of memory for objects

Java Memory Model: The Java memory model is an abstract concept that defines how threads interact with memory and how changes made by one thread become visible to other threads.It consists of the following components:

  1. Main Memory: The actual physical memory of the computer that stores all variables and objects.
  2. Working Memory (Thread Stacks): Each thread has its own working memory, which stores thread-local variables, operand stacks, and other data.
  3. Java Heap: The shared area of the main memory where objects and their corresponding instance variables are allocated.

The Java memory model specifies rules for how threads interact with memory, including read/write rules, synchronization mechanisms (synchronized blocks, methods, and volatile variables), and happens-before relationships that govern the ordering of memory operations between threads.

Remember, proper memory management is crucial for preventing memory leaks and other related problems in your Java applications.

Mapping to JVM Memory Areas

Let’s consider a simple Java program and see how different parts of it map to different areas of JVM memory:

Java
public class Main { // Class definitions go into Method Area

    private static int staticVar = 10; // Static variables also go into Method Area

    public static void main(String[] args) { // Method definitions go into Method Area
        int localVar = 20; // Local variables are stored in Stack memory of main thread

        MyClass obj = new MyClass(); // 'obj' reference is stored in Stack memory of main thread
                                     // The object it refers to is stored in Heap memory
    }
}

class MyClass { // Class definitions go into Method Area
    int instanceVar = 30; // Instance variables are part of the object stored in Heap memory
}

Here’s how different parts of this program map to JVM memory areas:

  • Method Area: This is where class definitions are stored. So, the definitions of Main and MyClass classes go here. The staticVar variable and main method also go into Method Area because they are static members of the Main class.
  • Heap Area: This is where all created objects are stored. So, the new MyClass() object is stored here. The instanceVar variable is part of this object.
  • Stack Area: Each thread in Java has its own JVM stack. Each method call creates a new frame on the stack which stores local variables and partial results. So, the main method’s call creates a new frame on the main thread’s stack. The localVar variable and obj reference are stored in this frame.
  • PC Registers: In this case, the PC register of the main thread will contain the address of the instruction currently being executed in the main method.
  • Native Method Stack: It contains all the native methods used in the application. Note that the memory for the native method stack is allocated per thread when each thread is created. This program does not use any native methods, so the Native Method Stack is not used here.

Program Counter (PC) Register and Native Method Stack

The Program Counter (PC) Register and Native Method Stack are parts of the Java Virtual Machine’s (JVM) internal architecture, and they are not directly accessible or manipulable from Java code. However, I can explain what they do in the context of running a Java program.

PC Register: The PC Register is a pointer to the current instruction that a thread is executing. Each thread in the JVM has its own PC Register. However, you cannot access or change the PC Register directly from your Java code. It’s managed by the JVM itself. For example, in a simple loop like this:

Java
for (int i = 0; i < 10; i++) { System.out.println(i); }

The PC Register will point to the next instruction to execute in this loop for the current thread.

Native Method Stack: This is where the JVM keeps native method information. Native methods are methods that are written in a language other than Java, and are called from Java code using the Java Native Interface (JNI). Here’s an example of calling a native method from Java:

Java
public class Main { 
    // Declare a native method 
    public native void printHelloWorld(); 

    static { 
        // Load the library containing the native method 
        System.loadLibrary("native"); 
    } 

    public static void main(String[] args) { 
        new Main().printHelloWorld(); 
    } 
}

In this example, printHelloWorld is a native method. When this method is called, information about the call is placed on the Native Method Stack.

Remember, these are low-level details of the JVM’s operation and aren’t typically something a Java programmer needs to deal with directly. They’re managed by the JVM itself.

    Java Memory Model before Java 8:

    Before Java 8, the Java memory model consisted of the following components:

    Heap Memory:

    • Young Generation (Nursery): All new objects are allocated in this memory. When this memory gets filled, Minor Garbage Collection is performed. It is divided into three sub-areas: Eden space and two Survivor spaces (Survivor 1 and Survivor 2).
    • Old Generation: All the long-lived objects which have survived many rounds of Minor Garbage Collection are stored in this area.
    • String Pool: This is a special storage area in the Java Heap where string literals are stored. When a string literal is created, the JVM first checks the String Pool before creating a new String object corresponding to it.

    PermGen (Permanent Generation): This was a special area in the heap memory that stored metadata about classes and their members, such as class names, methods, and fields. It also contained the string pool, which stored all the string literals used in the application.

    Method Area: This was a logical area within the PermGen space, used for storing the bytecode instructions and other metadata related to methods and constructors.

    Stack Memory: Each thread in the Java application had its own stack memory, which was used for storing local variables and method call information.

    Native Method Stack: This area was used for storing information related to native methods (methods written in languages other than Java, such as C or C++).

      Java Memory Model in Java 8 and Later Versions:

      In Java 8, the memory model underwent significant changes to address some limitations of the previous model. The main changes are:

      1. Removal of PermGen Space: The PermGen space was removed due to its fixed size limitation, which could lead to OutOfMemoryError exceptions. It was replaced by a new area called Metaspace.
      2. Introduction of Metaspace: Metaspace is a new memory area that replaces the PermGen space. It is responsible for storing class metadata and other related information. Unlike PermGen, Metaspace is not part of the Java heap and is instead allocated from native memory, which can grow automatically as needed.
      3. Compact String Representation: In Java 8, the internal representation of strings was optimized to reduce their memory footprint. This change was particularly beneficial for applications that use a large number of strings.
      4. Removal of Code Cache: Prior to Java 8, the JVM used a separate memory area called the Code Cache to store compiled code generated by the Just-In-Time (JIT) compiler. In Java 8, this area was removed, and the JVM now relies on the operating system’s memory mapping facilities to manage the compiled code.
      5. Parallel Array Sorting: Java 8 introduced parallel array sorting algorithms, which can take advantage of multiple CPU cores to sort arrays more efficiently.

      Java 8 memory model:

      Heap Area: This is the runtime data area from which the memory for all class instances and arrays is allocated. The Heap is divided into two main parts:
      Young Generation: All new objects are allocated in this memory. When this memory gets filled, Minor Garbage Collection is performed.

      Old Generation:
      All the long-lived objects which have survived many rounds of Minor Garbage Collection are stored in this area.

      String Pool: This is a special storage area in the Java Heap where string literals are stored. When a string literal is created, the JVM first checks the String Pool before creating a new String object corresponding to it.

      MetaSpace: In Java 8, the Permanent Generation (PermGen) was replaced with MetaSpace. MetaSpace is a non-heap memory area that stores metadata such as class definitions, method data, and field data. Unlike PermGen, MetaSpace can automatically increase its size.

      Stack Area: This is where the methods and local variables are stored. Each thread in a Java program has its own JVM stack created when the thread is created.

      PC Registers: Each JVM thread has its own pc (program counter) register. It’s used to hold the address of the JVM instruction currently being executed.

      Native Method Stack: It contains all the native methods used in the application.

      The other components of the Java memory model, such as the heap memory (with Young Generation and Old Generation), stack memory, and native method stack, remain largely unchanged in Java 8 and later versions.

      By removing the limitations of the PermGen space and introducing Metaspace, Java 8 improved memory management and reduced the risk of OutOfMemoryError exceptions. Additionally, the optimizations in string representation and parallel array sorting contribute to improved performance and memory efficiency in Java applications.

      Changes in Java 8 Memory Model:

      In Java 8, several changes were made to the memory model and JVM architecture to improve performance and memory management:

      1. Metaspace: Java 8 introduced a new memory area called Metaspace, which replaced the PermGen space (Permanent Generation) used in earlier Java versions. Metaspace is an area in native memory (outside the Java heap) where class metadata is stored. This change was made to address the limitation of the fixed-size PermGen space, which could lead to OutOfMemoryError exceptions.
      2. Removal of Code Cache: The Code Cache, which was used to store compiled code by the JIT compiler, was removed in Java 8. Instead, Java 8 relies on the operating system’s memory mapping facilities to manage the compiled code.
      3. Compact Strings: Java 8 introduced compact strings, which use a more efficient internal representation for storing string data. This optimization reduces the memory footprint of strings, especially for applications that use a large number of strings.
      4. Parallel Array Sorting: Java 8 improved the performance of sorting arrays by introducing parallel array sorting algorithms, which take advantage of multiple CPU cores for faster sorting.
      5. Removal of Permanent Generation (PermGen) Space: As mentioned earlier, Java 8 removed the PermGen space and replaced it with Metaspace, which is managed by the operating system’s native memory management system.

      In Java 8, the JVM memory area known as the Permanent Generation (PermGen) was removed and replaced by an area called the MetaSpace. Both PermGen and MetaSpace are used to store similar types of data such as class definitions, method data, and other metadata.

      The Method Area is a part of the JVM memory that was traditionally a part of PermGen. It was used to store class structure (like field and method data) and the code for methods and constructors. However, with the removal of PermGen in Java 8, the Method Area is now a part of MetaSpace.

      The key difference between PermGen and MetaSpace is that MetaSpace is not a contiguous JVM heap area. Instead, it uses native memory for the storage of class metadata. This means it’s limited by your system’s total available memory, rather than a fixed amount allocated on JVM startup. This change was made to avoid OutOfMemoryError: PermGen issues.

      Conclusion:


      Understanding the Java memory model and JVM architecture is crucial for developing efficient and performant Java applications. The memory model defines how threads interact with memory, ensuring thread safety and consistency. The JVM architecture, with its various components, provides a robust and efficient execution environment for Java programs. The changes introduced in Java 8, such as Metaspace, compact strings, and parallel array sorting, aim to improve performance, memory management, and overall efficiency. By grasping these concepts, Java developers can write better code, diagnose and resolve memory-related issues, and optimize their applications for optimal performance.

      Avatar

      Neelabh

      About Author

      As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

      You may also like

      Blog Java Java 8 Features

      Avoid NullPointerException in Java with Optional: A Comprehensive Guide

      Are you tired of dealing with avoid NullPointerExceptions(NPEs) in your Java code? Look no further than the Optional class introduced
      Blog Java Java 8 Features

      Unlocking the Power of Java 8 Streams: A Guide to Efficient Data Processing

      What are Java 8 Streams? At its core, a stream in Java 8 is a sequence of elements that facilitates