Java memory regions (runtime data regions) and the memory model (JMM) Java memory regions are different from the memory model. Memory regions refer to the fact that data is stored in different regions during the JVM runtime, emphasizing the division of memory space.

The Java Memory Model (JMM) defines the abstract relationship between threads and main Memory. The JMM defines how the JVM works in computer Memory (RAM). If we want to understand concurrent programming in Java, we need to understand the Java Memory Model.

Java Runtime Data Region It is well known that Java virtual machines have automatic memory management mechanisms, and if there are problems with memory leaks and overflows, troubleshooting for errors requires an understanding of how the virtual machine is using memory.

The figure below is after JDK8

The pre-JDK8 memory region is shown below:

In the HotSpot JVM, metadata and pools of constants such as Class and Method are used to hold classes and methods in the permanent generation. Whenever a class is first loaded, its metadata is put into the permanent generation. Permanent generation has a size limit, so if the loaded classes is too much, will likely result in the permanent generation of memory, namely the evil Java. Lang. OutOfMemoryError: PermGen, therefore we have to do to the virtual machine tuning. So why was PermGen removed from the Hotspot JVM in Java 8? I have summed up two main reasons:

Because the PermGen memory overflow, often causing annoying Java. Lang. OutOfMemoryError: The removal of PermGen will help the Hotspot JVM to integrate with the JRockit VM, since JRockit does not have permanent generation. For the various reasons above, PermGen was eventually removed, the method area moved to Metaspace, and the string constants moved to the Java Heap. Quote from https://www.sczyh30.com/posts…

Program Counter The Program Counter is a small memory space that can be viewed as an indicator of the line number of the bytecode being executed by the current thread.

Because the Java Virtual Machine’s multithreading is implemented by alternating threads and allocating processor execution time, at any given moment, a processor kernel will execute instructions from only one thread.

Therefore, in order to return to the correct execution position after the thread switch, each thread needs to have a separate program counter. Counters are not affected by each thread and stored independently. We call this memory area “thread private” memory.

If the thread is executing a Java method, this counter records the address of the virtual machine bytecode instruction being executed; If you are executing a Native method, this counter value is null (Undefined). This memory region is the only one that does not specify any OutOfMemoryError conditions in the Java Virtual Machine specification.

Like program counters, the Java Virtual Machine Stack (Java Virtual Machine Stacks) is thread private and has the same lifecycle as the thread.

The virtual Stack describes the in-memory model of the execution of Java methods: each method executes while creating a Stack Frame (the underlying data structure of the method runtime) to store local variables, operand stacks, dynamic links, method exits, and so on. Each method from the call to the execution of the process, corresponds to a stack frame in the virtual stack to push out of the process.

In an active thread, only the frame at the top of the stack is valid, called the current stack frame. The method being executed is called the current method, and the stack frame is the basic structure for the method to run. While the execution engine is running, all instructions can only operate on the current stack frame.

  1. A local variation scale is an area in which method parameters and local variables are stored. Local variables have no prepare phase and must be explicitly initialized. If the method is non-static, the instance reference of the object to which the method belongs is stored at the index[0] position, with a single reference variable of four bytes, followed by parameters and local variables. The STORE instruction in the bytecode instruction is to write the local variable files computed in the operation stack back to the storage space of the local variable table.

The virtual stack specifies two types of exceptions: if the thread request is deeper than the virtual machine allows, a StackOverflowError exception is thrown; If the virtual stack is dynamically scalable (as most current Java virtual machines are), an OutOfMemoryError exception is thrown if sufficient memory cannot be requested while scaling.

  1. Operation Stack An operation stack is a bucket structure that is initially empty. During method execution, various instructions write and extract information on the stack. The JVM’s execution engine is a stack-based execution engine, where the stack refers to the operation stack. The bytecode instruction set is defined based on the stack type, and the depth of the stack is in the stack attribute of the method meta information.

The difference between I ++ and ++ I:

I ++ : Load memory from the local variable table, then add and store memory from the local variable table, and use the top value of the operation stack, so that the thread reads from the operation stack the value before the self-increment. ++ I: first increment I of local variable scale, then extract and load memory, and then extract and use the top value of operation stack. The thread reads the value after increment from the operation stack. The reason I ++ is not an atomic operation, and even using volatile is not thread-safe, is that maybe I is pulled out of the local variable table (memory), pushed into the operation stack (register), incremalized from the operation stack, and used the top value of the stack to update the local variable table (register update is written to memory). Volatile guarantees visibility and ensures that the latest value is read from the local variable table each time, but these 3 steps may be interrupted by the 3 steps of another thread, resulting in data overwriting problems, resulting in the value of I being lower than expected.

  1. Dynamic Linking Each stack frame contains a reference to the current method in the constant pool to support dynamic linking of method invocation procedures.

When executing a method, there are two exit scenarios:

Normal exit, i.e. RETURN bytecode instructions that normally execute to any method, such as RETURN, IRETURN, ARETURN, etc.; Exception exits. In either case, the exit is returned to where the method was currently invoked. The exit process of the method is equivalent to ejecting the current stack frame. There are three ways to exit:

The return value is pushed to the upper call stack frame. Exception messages are thrown to stack frames that can be processed. The PC counter points to the next instruction after the method call. The Native Method Stack and the virtual Stack perform similar functions, except that the virtual Stack executes Java methods (that is, bytecode) for the virtual machine, while the Native Stack serves Native methods used by the virtual machine. The Sun Hotspot virtual machine directly combines the local method stack with the virtual stack. As with the virtual stack, the local method stack area throws StackOverflowError and OutOfMemoryError exceptions.

When a thread starts calling local methods, it enters a world that is no longer bound by the JVM. Local methods can use JNI(Java Native Interface) to access areas of data where the virtual machine is running, and can even call registers, with the same capabilities and permissions as the JVM. When a large number of native methods appear, it weakens the JVM’s control over the system because its error messages are black boxes. The local method stack will still throw NativeHeapOutOfMemory in case of insufficient memory.

The best known of the JNI class native methods is System.currentTimeMillis(), which allows Java to use the features of the operating System in depth and reuse non-Java code. However, during the course of the project, if JNI is implemented in a large number of other languages, the cross-platform nature will be lost.

Java Heap For most applications, the Java Heap is the largest chunk of memory managed by the Java Virtual Machine. The Java heap is an area of memory shared by all threads that is created when the virtual machine is started. The only purpose of this memory area is to hold object instances, and almost all object instances allocate memory here.

The Heap is the primary area managed by the Garbage collector, so it is often referred to as the “Garbage Collected Heap.” From the point of view of memory collection, the Java heap can also be subdivided into: the new generation and the old generation, since the current collectors are mostly generational collection algorithms. More detailed examples include Eden space, From Survivor space, To Survivor space, and so on. From the perspective of memory Allocation, it is possible to divide multiple Thread Local Allocation Buffers (TLAB) into the Java heap that is shared by threads.

The Java heap can be in a physically discontiguous memory space, as long as it is logically contiguous, and the current mainstream virtual machines are implemented as extensible (through -Xmx and -XMS control). An OutOfMemoryError exception is thrown if there is no memory in the heap to complete instance allocation and the heap can no longer be expanded.

Method Area The Method Area, like the Java heap, is a memory Area shared by threads. It is used to store class information, constants, static variables, code compiled by the just-in-time compiler, and other data that has been loaded by the virtual machine. Although the Java Virtual Machine specification describes the method area as a logical part of the Heap, it has an alias called non-heap, which is supposed to distinguish it from the Java Heap.

The Java Virtual Machine specification is very liberal in its restrictions on method extents, and in addition to not requiring contiguous memory like the Java heap and having the option of a fixed or extensible size, it also has the option of not implementing garbage collection. Garbage collection is relatively rare in this area, and the memory collection targets are mainly collection of constant pools and unload of types. An OutOfMemoryError exception is thrown when the method area cannot meet the memory allocation requirements.

Prior to JDK8, the implementation of method areas in HotSpot was Perm. JDK8 began to use Metaspace (Metaspace). Previously, string constants of all content were moved to heap memory and other content was moved to Metaspace, which was allocated directly in local memory.

Why use metaspaces instead of implementations of permanent generations?

The string exists in a permanent generation, which is prone to performance problems and memory leaks. It is difficult to determine the size of the class and method information, so it is difficult to specify the size of the permanent generation. If it is too small, it is easy to cause the overflow of the permanent generation; if it is too large, it is easy to cause the overflow of the old generation. Permanent generation adds unnecessary complexity to the GC and is inefficient for collection. Combine Hotspot with JRockit. Runtime Constant Pool The Runtime Constant Pool is part of the method section. In addition to the description information of the Class, such as the version, field, method and interface of the Class, there is also a Constant Pool of information, which is used to store all kinds of literals and symbolic references generated during compilation. This information will be stored in the run-time Constant Pool of the method area after the Class is loaded.

In general, in addition to the symbolic references described in the Class file, the translated direct references are also stored in the run-time constant pool.

Runtime constant pool relative to the Class file another important feature of the constant pool is dynamic, the Java language does not require constant must only compile time to produce, is not preset constant pool into the Class file content can enter method area runtime constant pool, runtime might also put new constants in a pool, One feature that has been used a lot by developers is the String class’s intern() method.

Since the run-time constant pool is part of the method area, it is naturally limited by the memory of the method area, and an OutOfMemoryError exception is thrown when the constant pool can no longer be applied to memory.

Direct Memory is not part of the virtual machine runtime data area, nor is it a Memory area defined by the Java Virtual Machine specification.

The addition of NIO in JDK 1.4 introduces a Channel – and buffer-based I/O approach that allows direct allocation of out-of-heap memory using Native libraries. It is then acted on as a reference to this memory through a DirectByteBuffer object stored in the Java heap. This can significantly improve performance in some scenarios because you avoid copying data back and forth between the Java heap and the Native heap.

Obviously, the allocation of native direct memory is not limited by the size of the Java heap, but since it is memory, it is certainly limited by the size of total native memory (including RAM and SWAP area or paging files) and processor addressing space. When configuring virtual machine parameters, server administrators set parameter information such as -Xmx based on actual memory, but often ignore direct memory, making the sum of memory regions greater than the physical memory limit (both physical and operating system level limits), resulting in OutOfMemoryErrors during dynamic scaling.

Java Memory Model The Java Memory Model is a concurrency model of shared memory in which threads communicate implicitly by reading and writing shared variables (instance fields, static fields, and array elements in the heap memory).

The Java Memory Model (JMM) controls communication between Java threads, determining when a thread’s write to a shared variable is visible to another thread.

Computer Caching and Cache Consistency Computers use caching between fast CPUs and relatively slow storage devices as a buffer between memory and processors. Copy the data needed by the operation to the cache, so that the operation can run quickly. When the operation is over, it will be synchronized back to the memory from the cache.

In a multi-processor system (or a single-processor multi-core system), each processor core has its own cache, and they share the same Main Memory.

When multiple processors are involved in the same main memory area, it is possible that their cache data will be inconsistent.

For this reason, each processor needs to follow some protocol when accessing the cache, and operate according to the protocol when reading and writing, to maintain the consistency of the cache.

The main goal of the Java memory model is to define the rules for accessing variables in a program, namely, the low-level details of storing variables (variables shared by threads) into memory in the virtual machine and retrieving them from memory.

The Java memory model stipulates that all variables are stored in the main memory, and each thread has its own working memory. All operations on variables by threads must be carried out in the working memory, and variables in the main memory cannot be read or written directly.

The working memory here is an abstraction of the JMM, also known as local memory, which stores the thread to read/write copies of shared variables.

Just as each processor kernel has a private cache, each thread in the JMM has private local memory.

Different threads cannot directly access the variables in each other’s working memory, and the communication between threads is usually carried out in two ways, one is through message passing, the other is shared memory. Java threads communicate in shared memory. The interaction between threads, main memory and working memory is shown in the following figure:

The main memory, working memory and the Java heap, stack, method area in the Java memory area are not the same level of memory division, these two basically have no relationship, if the two must be forced to correspond to each other, then from the definition of variables, main memory, working memory, Main memory corresponds primarily to the object instance data portion of the Java heap, while working memory corresponds to a portion of the virtual stack.

Reorder and happens-before rules Compilers and processors often reorder instructions to improve performance when executing programs. There are three types of reordering:

Compiler optimized reordering. The compiler can rearrange the execution order of statements without changing the semantics of a single-threaded program. Instruction level parallel reordering. Modern processors use Instruction Level Parallelism (ILP) to execute multiple instructions on top of each other. If there is no data dependency, the processor can change the execution order of the statement corresponding to the machine instructions. The reordering of memory systems. Because the processor uses caching and read/write buffers, the load and store operations may appear to be performing out of order. From the Java source code to the sequence of instructions that are actually executed, there are three kinds of reordering:

The JMM is a language-level memory model that ensures consistent memory visibility for programmers across compilers and across processor platforms by disallowing certain types of compiler reordering and processor reordering.

The Java compiler disallows processor reordering by inserting a memory barrier at the appropriate place in the generated instruction sequence (reordering cannot reorder subsequent instructions to the place before the memory barrier).

Starting with JDK5, the Java memory model introduced the concept of happens-before to illustrate memory visibility between operations.

If the result of the execution of one operation needs to be visible to the other operation, there must be a happens-before relationship between the two operations. The two operations mentioned here can be either within a thread or between threads.

By “visibility”, I mean that when one thread modifies the value of the variable, the new value becomes immediately known to other threads.

If A happens-before B, then the Java memory model assures the programmer that the results of A’s actions will be visible to B, and that A’s execution order will precede B’s.

Important happens-before rules are as follows:

Rule of program sequence: Each operation in a thread happens -before any subsequent operation in that thread. Monitor lock rule: The unlocking of a monitor lock happens before the subsequent locking of the monitor lock. Volatile variable rule: A write to a volatile field happens-before any subsequent reads to that field. Transitivity: If A happens-before B, and B happens-before C, then A happens-before C. The following figure is the relationship between happens-before and JMM

Volatile is arguably the lightest synchronization mechanism provided by the JVM. When a variable is defined as volatile, it has two properties:

Make this variable visible to all threads. Ordinary variables can not do this, the value of ordinary variables between threads to pass through the main memory to complete. Note that while volatile guarantees visibility, operations in Java are not atomic, making operations on volatile variables unsafe under concurrency. The synchronized keyword is made thread-safe by the rule that only one thread is allowed to lock a variable at any one time.

Disallow instruction reorder optimization. A normal variable only guarantees the correct result in all the places that depend on the assignment during the execution of the method. It does not guarantee that the order in which the assignment is performed is the same in the program code. Finally, a deep understanding of the Java Virtual Machine (Version 2) code efficiency: Java development manual Java memory model principles, do you really understand? Deep understanding of the Java memory model