Memory Model and Java Memory Regions

Memory Model and Java Memory Regions

In computer science, a memory model is a model that describes how memory is accessed and managed in a computer system. In the Java programming language, the memory model describes how the Java Virtual Machine (JVM) manages the memory of Java code, including issues such as memory allocation, memory recycling, and memory visibility. This article will introduce the Java memory model and Java memory regions in detail, and provide related Java code examples.

insert image description here

Java memory model

The Java Memory Model is a specification for memory access by the Java Virtual Machine (JVM). The Java memory model defines the rules for sharing data between threads in a Java program to ensure the correctness of the Java program in a multi-threaded environment. The Java memory model mainly includes the following aspects:

memory visibility

Memory visibility refers to whether a thread's modification of a shared variable can be immediately seen by other threads. In Java, if a thread modifies the value of a shared variable, other threads may not be able to immediately see the new value of the variable. This is because the Java memory model allows each thread to have its own local memory, and variables in these local memories may be out of sync with variables in main memory. In order to solve this problem, the Java memory model stipulates that when a thread modifies a shared variable, it must refresh the modified value to the main memory, and when other threads read the shared variable, they must read the latest thread from the main memory. value.

atomicity

Atomicity means that an operation is either executed in its entirety or not executed at all. In Java, some operations are atomic, such as reading and writing basic data types (int, long, float, double, etc.), while some operations are not atomic, such as reading and writing non-volatile reference variables. For non-atomic operations, multiple threads may modify the same variable at the same time, resulting in data inconsistency. In order to solve this problem, Java provides some synchronization mechanisms, such as synchronized and volatile keywords, which can guarantee the atomicity of operations.

orderliness

Orderliness refers to the order in which instructions in a program are executed. In Java, due to optimizations by the JVM, instructions in a program may be reordered, causing the program to behave differently than expected. In order to solve this problem, Java provides some synchronization mechanisms, such as synchronized and volatile keywords, which can ensure the order of instructions.

Java memory area

The Java virtual machine divides memory into several areas, each area has a different role and life cycle. The Java memory area mainly includes the following areas:

program counter

The program counter is a small area of ​​memory that stores the address of the bytecode instruction that the current thread is executing. In Java, since thread switching is controlled by the operating system, a program counter is needed to record the execution position of the current thread so that execution can be resumed after thread switching.

virtual machine stack

The virtual machine stack is a memory area used to store Java method calls and return values. When each thread executes a Java method, it creates a corresponding virtual machine stack. The size of the virtual machine stack can be specified when starting the JVM. If the stack space is insufficient, a StackOverflowError exception will be thrown.

native method stack

The native method stack is similar to the virtual machine stack, except that it serves for the execution of native methods (Native Method). Native methods refer to methods written in a native language (such as C or C++), which can directly call the underlying functions of the operating system. Like the virtual machine stack, the size of the native method stack can also be specified when starting the JVM.

heap

The heap is the largest memory area in Java and is used to store Java objects. When we use the new keyword to create an object, the object will be stored on the heap. The size of the heap can be specified when starting the JVM. If the heap space is insufficient, an OutOfMemoryError exception will be thrown.

method area

The method area is a memory area used to store metadata and constant pool of the class. In Java, each class has a corresponding Class object, which stores the metadata of the class (such as class name, parent class, interface, method, etc.). The constant pool is a memory area used to store constants, including string constants, numeric constants, class names, and method names. The size of the method area can be specified when starting the JVM. If the space in the method area is insufficient, an OutOfMemoryError exception will be thrown.

runtime constant pool

The runtime constant pool is the part of the method area used to store constants that cannot be determined during compilation. For example, if we use string concatenation to concatenate two strings, the concatenated string is not determined during compilation, but at runtime. In Java, the runtime constant pool is dynamically generated when the class is loaded, and it includes all the constant pool information in the class.

Java code example

Here are some Java code samples to demonstrate the concepts of the Java memory model and Java memory regions:

memory visibility

public class MemoryVisibilityDemo {
    private static boolean flag = false;
 
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!flag) {
                // do something
            }
            System.out.println("flag is true");
        }).start();
 
        Thread.sleep(1000);
        flag = true;
    }
}

The code above demonstrates a memory visibility problem. In this example, we create a thread that keeps checking the value of the flag variable until the flag variable becomes true and outputs a message. In the main thread, we set the flag variable to true and wait for 1 second. If there are no issues with memory visibility, then we should be able to see output messages. But in fact, sometimes the program does not output any messages, because the modification of the flag variable has not been flushed to the main memory in time, causing another thread to be unable to see the new value of the flag variable.

In order to solve the memory visibility problem, we can use the volatile keyword to modify the flag variable, which can force all threads to read the value of the flag variable from the main memory:

public class MemoryVisibilityDemo {
    private static volatile boolean flag = false;
 
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!flag) {
                // do something
            }
            System.out.println("flag is true");
        }).start();
 
        Thread.sleep(1000);
        flag = true;
    }
}

atomicity

public class AtomicityDemo {
    private static int count = 0;
 
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    count++;
                }
            }).start();
        }
 
        Thread.sleep(5000);
        System.out.println(count);
    }
}

The code above demonstrates the atomicity problem. In this example, we create 1000 threads, and each thread will add 1 to the count variable and repeat the execution 1000 times. Theoretically, the final count value should be 1000*1000=1000000. But in fact, since the count variable is not atomic,

Guess you like

Origin blog.csdn.net/JasonXu94/article/details/131755968