Java memory model, divided into thread private area, thread shared area and direct memory. Thread-private areas include program counters, virtual machine stacks, and local method stacks. The thread shared area includes the heap and method area.

Thread-private areas have the same life cycle as threads, created at the start of user threads and destroyed at the end of user threads.

The life cycle of a thread shared area is the same as that of a virtual machine. It is created when the virtual machine starts and destroyed when the virtual machine stops.

Program counter

The Program Counter Register is used to store the address of the next byte code instruction to be run. Is thread private. Is the only area of memory in the Java runtime data area that does not throw OutOfMemoryError.

If a Java method is executing at this point, the counter records the address of the virtual machine bytecode instruction. Null if the Native method is being executed at this time.

The virtual machine stack

The Java Virtual Machine Stack is the memory model for threads, and the Stack frames in the Virtual Machine Stack are the memory model for Java methods. A Stack Frame is an element in the virtual machine Stack. Each time a method is executed, the JVM synchronously creates a Stack Frame to store local variables, operand stacks, dynamic links, method exits, and some additional information. Each method from the start of the call to the end of the execution corresponds to the stack frame from the push to the out of the stack (whether the method returns normally or throws an exception counts as the end of the method execution).

The virtual machine stack is thread private. A StackOverFlowError is raised if a thread requests a stack depth greater than the one allowed by the virtual machine. If the depth of a virtual stack can be dynamically expanded, an OutOfMemoryError will be thrown when the stack cannot be allocated enough memory to extend.

Local variable scale

A Local Variable Table is used to store method parameters and Local variables defined within a method. Its storage unit is Variable Slot.

There are three points that need to be paid attention to in the local variation scale:

  1. Variable slot size:

    The virtual machine specification does not specify the size of the variable slots, only that a variable slot should be able to hold 4 bytes of data types. When the variable slots are 4 bytes, long and double types are allocated two consecutive slots in a high-order alignment, and neither of the slots can be accessed separately. Since the virtual machine stack is thread-private, there is no need to worry about thread-safety issues with such nonatomic operations.

  2. Maximum capacity of the local variable scale:

    The maximum capacity of the local variable table is known at compile time. The max_locals data item in the code property of the class file determines the maximum size of the local variable table. If the method being executed is an instance method, the this pointer is placed in the 0th slot.

  3. Variable slot reuse:

    The scope of a local variable in a method body usually does not cover the entire method body, so when the instruction number of a program counter exceeds the scope of a local variable, the slot is handed over to another variable. This affects garbage collection, however, because if the slot is not successfully reused, the reference to the original object remains. If this object is not used later, because the local variable table is part of the GC Root, floating garbage can be a problem.

    public static void main(String[] args) {{byte[] buffer = new byte[64 * 1024 * 1024];
        }
        System.gc();
    }
    Copy the code

    The GC results:

    [GC (system.gc ()) 70620K->66416K(241664K), 0.0013887 secs] [Full GC (system.gc ()) 66416K->66195K(241664K), 0.0051875 secs]Copy the code

    Even if a buffer slot is out of scope, it remains a reference to the object because it is not reused, so GC does not reclaim its memory. If this variable slot is reused it will be reclaimed:

    public static void main(String[] args) {{byte[] buffer = new byte[64 * 1024 * 1024];
        }
        int a = 0;
        System.gc();
    }
    Copy the code

    The GC results:

    [GC (system.gc ()) 70620K->66416K(241664K), 0.0016502 secs] [Full GC (system.gc ()) 66416K->659K(241664K), 0.0052463 secs]Copy the code

The operand stack

The Operand Stack is the Stack used to store the operands of bytecode instructions. During the execution of the method, the JVM pushes the operands involved in the various operations onto the stack, and then calls the instructions for execution. The JVM’s interpretative execution engine is called a stack based execution engine, referring to the operand stack.

There are two things to note about the operand stack:

  1. Maximum depth of operand stack:

    The maximum depth of the operand stack is known at compile time. The max_STACKS data item in the Code property of the class file records the maximum depth of the operand stack.

  2. Stack frame overlap:

    Because the variables in the operand stack often become the parameters of the new calling method, the virtual machine implementation often does some optimization by overlapping the operand stack of the lower stack frame with the local variables of the upper stack frame, which not only saves memory space, but also saves the consumption of parameter passing.

Dynamic connection

Dynamic Linking is used to hold the memory addresses resolved at run time by the Dynamic call point qualifier in the method body.

When compiling a Java file to a class file, there are a large number of symbolic references in the constant pool of the class file. It is impossible to know which memory addresses these symbolic references point to at compile time. Only the memory addresses of these symbolic references in the method area can be resolved at run time. Specifically, it refers to the parsing phase in the class-loading mechanism. If a symbolic reference is resolved and subsequent use of the symbolic reference returns the previously resolved result, it is called static resolution. If the symbolic reference needs to be reresolved each time it is used, it is called dynamic resolution.

The mapping information of the statically resolved symbol references to memory addresses is stored in the runtime constant pool of the method area. Because the result of dynamic parsing is different each time, it is not possible to determine which memory address is pointed to, so you need to save the result of dynamic parsing in the stack frame.

A symbolic reference that requires dynamic resolution is called a “dynamic call point qualifier” because the bytecode instruction that invokes the symbolic reference is called invokedynamic.

Methods the export

The method exit (returnAddress) is used to store the address of the current method in the calling method. When the method is finished, it returns the location of the calling method execution through the method exit. Upon exception call completion, determine the method exit from the exception manager.

Methods exit in two ways: normal call completion and exception call completion, either way returning to where the calling method was executed. Therefore, you need to store the return address of the current method in the stack frame, and the value of the program counter during the stack frame exit is that return address. When an exception call completes, the return address is determined by the exception handler, and the return address in the stack frame is not used.

Local method stack

The VM Stack serves Java methods, and the Native Method Stack serves Native methods. Both the local method stack and the virtual machine stack are thread private. Exceptions and conditions thrown are also consistent with the virtual machine stack. A StackOverFlowError is raised if a thread requests a stack depth greater than the one allowed by the virtual machine. If the depth of a virtual stack can be dynamically expanded, an OutOfMemoryError will be thrown when the stack cannot be allocated enough memory to extend.

However, the HotSpot virtual machine combines the local method stack with the virtual machine stack.

The Java heap

The Java Heap is an area of data shared by threads that holds object instances, and at this stage almost all object instances are allocated on the Heap. The heap is also the most important data area for garbage collection. An OutOfMemoryError is thrown when the heap is out of memory.

This is the most important data store area in the JVM, and also the most complex.

Local method area

The local Method Area is a data Area shared by threads. It is used to store data such as type information, constants, static variables, and code cache loaded by VMS. An OutOfMemoryError is thrown when the method area runs out of memory.

Method area with permanent generation

The JVM specification describes the method area as a logical part of the heap, but it has the alias “non-heap” to distinguish it from the heap.

Prior to JDK8, the HotSpot virtual machine design team used the permanent generation to implement the method area so that the HotSpot garbage collector could manage the permanent generation memory as well as the heap. This design makes it easier for Java applications to run out of memory because the permanent generation has an upper limit and default values even if it is not set.

Starting with JDK7, HotSpot gradually began to “deperpetuate” the program by moving the runtime constant pool to the Java heap in JDK7.

In JDK8, the concept of a permanent generation is completely abandoned and replaced by a Metaspace implemented in local memory, the method area.

Runtime constant pool

The Runtime Constant Pool is part of the method area. Used to store various constants, literals, and symbolic references contained in the class file when the class is loaded. Meanwhile, the result of statically resolved symbolic references at run time is also stored in the constant pool. An OutOfMemoryError is thrown when the constant pool fails to allocate memory at runtime.

After JDK7, the constant pool is moved to the heap, so the VIRTUAL machine can garbage collect the constant pool. If the objects in the constant pool are not referenced, the constant pool will be reclaimed.

The String::intern method actively adds a String to the constant pool. If there is a String to add, the String is returned. If there is no String, the String is added to the pool and returned. Determine if there is a logical comparison using the String::equals method.

Since the constant pool is moved to the heap at the start of JDK7, the String:: Intern behaves differently in JDK6 and JDK7:

In JDK6, if the string does not exist in the constant pool, the string to be added will be created in the constant pool and returned. If the string in JDK7 does not exist in the constant pool, it will add a reference to the current string in the constant pool and return the reference. Hence the following differences:

String str = new StringBuilder("HELLO").append(" WORLD").toString();
System.out.println(str == str.intern());
Copy the code

The above code returns false in JDK6 because they are two different objects, and true in JDK7 because they both point to the same object in the heap.

Direct memory

Direct Memory is not part of the Memory region of the JVM, but the JVM can also manipulate Direct Memory. In particular, NIO, introduced in JDK1.4, provides Channel and buffer-based I/O, which allows Native libraries to directly allocate out-of-heap memory and then operate on this memory using DirectByteBuffer objects as references. This avoids copying data back and forth between the Java and Native heaps, and can significantly improve performance in some scenarios.

Direct memory also throws an OutOfMemoryError when the host is out of memory.