This is the 24th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

1. An overview of the

The JVM memory model, or runtime data area, consists of five parts:

  • The heap
  • The virtual machine stack
  • Local method stack
  • Methods area
  • Program counter

Among them, the heap and method area are shared by threads, while the virtual machine stack, Native stack and program counter are private to threads.

2. Pile (Heap)

Almost all object instances and arrays are allocated on the heap, making it the largest chunk of memory managed by the VM and the main area of activity for the garbage collector.

Since modern VMS use generational collection algorithms, the Java heap can also be subdivided into: new generation (Eden zone, From Survivor zone, and To Survivor zone) and old generation From the perspective of GC. From a memory allocation perspective, the thread-shared Java heap can also be divided into multiple thread-private allocation buffers (TLabs). The purpose of further partitioning is to better reclaim memory and faster allocation of memory.

According to the virtual machine specification, the heap can be on a physically discontinuous memory space, as long as it is logically continuous. If the memory space is insufficient, OutOfMemoryError is reported.

2.1 the new generation

All newly generated objects are placed in the new generation first. The goal of the new generation is to collect objects with short life spans as quickly as possible.

The Cenozoic era is divided into three regions: one Eden region and two Survivor regions. Most objects are generated in the Eden zone. When Eden is full, the surviving objects will be copied to the Survivor zone (one of the two). When this Survivor zone is full, the surviving objects in this zone will be copied to another Survivor zone. When this Survivor zone is full, Objects copied from the first Survivor zone that are still alive will be copied “Tenured.”

It should be noted that the two Survivor zones are symmetric and have no sequence relationship, so there may be objects copied from Eden and objects copied from the previous Survivor in the same zone, while only objects copied from the first Survivor to the old age can be copied. Also, one of the Survivor zones is always empty.

2.2 the old s

The object will be moved to the old age if:

  • Objects that survive N garbage collections in the new generation
  • The large object exceeds the preset value OF PretenureSizeThreshold.

Therefore, older generations tend to store objects with long life cycles or large objects. It generally has a large memory capacity, and full GC is triggered when it runs out of memory.

2.3 the permanent generation

See the methods section below.

Java Virtual Machine Stack (JVM Stack)

The virtual machine Stack describes the memory model of Java method execution: a Stack Frame is created for each method execution to store local variables, operand stacks, dynamic links, return addresses, and other information. Each method that is called back corresponds to a stack frame that is pushed in and out of the virtual machine stack (the VM provides -xss to specify the maximum stack space for a thread, which directly determines the maximum depth of a function call).

The size of the stack can be controlled with the -xss parameter. If the stack depth is insufficient, a StackOverflowError is reported. OutOfMemoryError is reported if the stack is out of memory.

2.1 Stack Frame (Frame)

Each method call creates a new stack frame and pushes it to the top of the stack. The stack frame goes off the stack when the method returns normally or when an uncaught exception is thrown during the call. The stack cannot be manipulated directly, except for the pressing and unloading of the stack frame.

Each stack frame contains:

  • Local variable scale
  • The operand stack
  • Dynamic link
  • The return address

2.1.1 Local variation scale

The local variable table contains all variables in the method execution, including this reference, all method parameters, and other local variables. It’s made up of arrays. For class methods (that is, static methods), the method parameters start at subscript 0; For object methods, position 0 is left as this.

Includes the following variable types:

  1. boolean
  2. byte
  3. short
  4. char
  5. long
  6. int
  7. float
  8. double
  9. reference
  10. returnAddress

The capacity unit of a local variable table is Slot, 4 bytes, 32 bits. Therefore, all variable types except long and double occupy a slot. Long and double need to occupy two consecutive slots.

2.1.2. Operand stack

The operand stack is used to store operands during bytecode execution. When a method is first executed, its operand stack is empty, and various bytecode instructions write and read operands to and from the operand stack as the method executes. For example, when executing the iADD instruction for integer addition, the execution engine takes the two elements at the top of the operand stack (off the stack), adds them together to get the result and pushes them onto the stack.

int i;

Compiled into the following bytecode:

0: iconst_0 // push 0 to the top of the operand stack 1: istore_1 // fetch element 0 from the top of the operand stack and store it in the local variable table 1

2.1.3. Dynamic linking

Each stack frame contains a reference to the method that the stack frame belongs to in the runtime constant pool, and this reference is held to support dynamic concatenation during method calls. A large number of symbolic references exist in the constant pool of the Class file, and the method invocation instructions in the bytecode take symbolic references to methods in the constant pool as arguments. Some of these symbolic references are converted to direct references during class loading or the first time they are used, which is called static resolution. The other part is converted to a direct reference during each run, which is called the dynamic join.

2.1.4. Return address

When a method completes, there are two ways to return:

  • Normal return: The execution engine executes to any returned bytecode instruction
  • Exception return: Exits when an exception is encountered during method execution. Exceptions include exceptions inside the virtual machine and exceptions thrown in code using Athrow bytecode

Either way, the method needs to return to where it was called before exiting. Normally, when a method exits normally, the value of the caller’s PC counter is the return address; When an exception exits, the return address is determined by the exception handler.

3. Native Stack

What is a Native Method?

To put it simply, a Native Method is an interface for Java to call non-Java code. It provides us with a very concise interface, and we do not need to understand the tedious details outside Java applications.

Here is an example of a local method:

 public class IHaveNatives {

      native public void Native1( int x ) ;

      native static public long Native2() ;

      native synchronized private float Native3( Object o ) ;

      native void Native4( int[] ary ) throws Exception ;

 }
Copy the code

Why call non-Java code? Mainly because the JVM may interact with the underlying operating system, the operating system’s library functions are most likely implemented in C or other languages other than Java. The function of the local method stack is very similar to that of the virtual machine stack, except that operations are performed on local methods. The virtual machine specification does not require the implementation language, usage mode, or data structure of the local method stack, so specific virtual machines can implement it freely.

Like the virtual stack, the local method stack throws stackOverflowErrors and OutofMemoryErrors.

4. The method area

Known as Permanent Generation, it is a contiguous-memory space that stores classes loaded by the JVM, constants, static variables, code compiled by the just-in-time compiler, and so on. It is also called non-heap, which means it can be distinguished from a heap.

HotSpot VM extends GC generation collection to the method area, using persistent generations of the Java heap to implement the method area so that HotSpot garbage collector can manage this part of memory as well as the Java heap. You don’t have to develop a dedicated memory manager for the method area (the main goal of memory reclamation for permanent bands is collection and type offloading for constant pools, so the benefits are generally small)

The size of the permanent generation can be controlled by setting the value -xx :MaxPermSize. The default size of the permanent generation is 64M for 32-bit machines and 85M for 64-bit machines.

Garbage collection for the permanent generation is tied to garbage collection for the old generation, and once one of the areas is occupied, both areas are collected. But there is an obvious problem, because we can set the size of permanent generation through XX:MaxPermSize, once the class metadata exceeds the set size, the program will run out of memory, and memory overflow error (OOM).

However, HotSpot in 1.7 has removed the string constant pool from the permanent generation. In 1.8, the permanent section was completely removed and replaced with Metaspace. (See the “meta-space” section below for details.)

4.1. This reference

All classes are loaded with a reference to the loader that loaded itself, and in turn each class loader contains references to all the classes they loaded.

4.2. Runtime constant pool

It is used to store the various literal and symbolic references generated at compile time in the runtime constant pool of the methods area. However, the Java language does not require constants to be generated only at compile time. That is, new constants may be put into the method area runtime constant pool if they are not preloaded into the constant pool in the Class file, as in the Case of the String intern() method.

Literals include:

  • The name of the class
  • Variable names and values (instance, static variables)
  • Constant notables and values
  • The method name
  • The property name

Symbolic references, like #2, #13…

The following code:

Object foo = new Object();
Copy the code

Compile to bytecode:

0: new #2 // Class java/lang/Object 1: dup 2: invokespecial #3 // Method java/ lang/Object "< init>" ( ) VCopy the code

The new opcode is immediately followed by operand #2. The operand is an index of the constant pool, indicating that it refers to the second entity of the constant pool. The second entity is a reference to a Class that in turn refers to another entity (// Class Java /lang/Object) that contains the utF8-encoded string Class name in the constant pool. This symbolic reference is then used to find the java.lang.Object class. The new opcode creates an instance of the class and initializes the variables. A reference to the new class instance is added to the operand stack.

The DUP opcode creates an additional copy of the reference to the top element of the operand stack. Finally, call the instance initialization method in line 2 with Invokespecial. The opcode also contains a reference to the constant pool. The initialization method takes the operand reference off the top of the stack as an argument to the method. Finally, there is only one reference to the new object, which has already been created and initialized.

4.3. Field data

  • The field name
  • type
  • The modifier
  • Attributes

4.4. Method data

  • The method name
  • Return value type
  • Parameter types (in order)
  • The modifier
  • attribute

4.5. Method code

  • The bytecode
  • Operand stack size
  • Local variable size
  • Local variable scale

4.6. Exception table

The exception table stores each exception handling information like this:

  • The starting point
  • The end point
  • The program counter (PC) offset of the exception handling code
  • The constant pool subscript corresponding to the caught exception class

If a method defines a try-catch or try-finally exception handler, an exception table is created. It stores the necessary information for each exception handler and finally code block, including the area of the code block covered by the processor and the type of exception handled.

When a method throws an exception, the JVM looks for a matching exception handler. If not, the method ends immediately and pops up the current stack frame, and the exception is thrown back into the method that called the method (in the new stack frame). If all stack frames are ejected and no matching exception handler is found, the thread terminates. If this exception is thrown in the last non-daemon (such as the main thread), it can also cause the JVM process to terminate.

The Finally exception handler matches all exception types and executes any exception that throws a Finally block. In this case, the finally block is still executed at the end of the method when no exceptions are thrown. This is done by jumping to the finally code block before the code return.

4.7. Other

All threads share the same method area, so processes accessing method area data and dynamically linked processes must be thread-safe. If two threads attempt to access a field or method of a class that has not yet been loaded, they must load it only once, and both threads must wait for it to finish loading before they can continue.

The Oracle JVM’s JConsle shows that the method area and code cache area are treated as non-heap memory, while the OpenJDK shows that CodeCache is treated as a separate domain in the VM’s ObjectHeap.

5. Program counter (PC)

A small area of memory that is used as a line number indicator of bytecode execution by the current thread. In the JVM model, the bytecode interpreter selects the next bytecode instruction to be executed by changing the value of PC. Branch, loop, jump, exception handling, thread recovery and other basic functions need to be completed by PC.

  • If the thread is executing a Java method, the PC records the address of the virtual machine bytecode instruction.
  • If the Native method is executed, the PC value is null (Undefined).

Unlike OS scheduling on a process-by-process basis, concurrency in the JVM is achieved by switching threads and allocating time slices for execution. At any one time, a processor kernel executes instructions in only one thread. Therefore, in order for threads to switch back to the correct execution position, each thread needs to have a separate program counter, which is called “thread-private” memory.

This memory area is also the only area where the VIRTUAL machine specification does not define OutOfMemoryError exceptions.

6. Yuan space

Prior to JDK8, class metadata and constants were stored in a data area adjacent to heap memory, known as the persistent generation. However, there is a problem in this case. If the metadata size of the class exceeds the allocated memory of the application, then there is a memory overflow problem.

After JDK8, the permanent generation is removed, and the data that was stored in the permanent generation is stored in a local area of memory called the meta-space.

The meta space is stored in local memory.

6.1. Capacity of the meta space

  1. By default, class metadata is limited only by available local memory.
  2. The MaxMetaspaceSize parameter is used to limit the size of local memory allocated to class metadata. If this parameter is not specified, the meta-space adjusts dynamically at run time as needed.
  3. If the GC finds that a class loader is no longer alive, it reclaims the associated space entirely.

6.2. Memory management of the meta space

The memory management of the meta-space is done by the meta-space virtual machine. As long as the classloader is alive, the metadata for the classes it loads is also alive and therefore not recycled.

Metaspace VMS are responsible for allocating metaspace in the form of block allocation. The size of the block varies with the type of class loader. There is a global list of free blocks in a meta-space virtual machine. When a classloader needs a block, it gets and maintains its own block list from this global block list. When a classloader is no longer alive, its holding blocks are released and returned to the global block list. The blocks held by the class loader are then divided into blocks, each of which stores a unit of meta-information. Blocks in blocks are allocated linearly (in the form of pointer collision allocation). Blocks are allocated from memory-mapped areas. These global virtual memory mapped areas are connected in a linked list that is returned to the operating system once a virtual memory mapped area is cleared.

6.3. Meta-space tuning

For a 64-bit server-side JVM, the default — XX:MetaspaceSize value is 21MB. This is the initial high water mark. Once this watermark is reached, the Full GC will be triggered to unload unused classes (that is, their corresponding class loaders are no longer alive), and the high watermark will reset. The value of the new high water level depends on how much space is freed after GC. If there is not enough free space, the high water level rises. If too much space is released, the high water level drops. If the initial high water mark is set too low, the above high water mark adjustment can occur many times. From the garbage collector logs, we can observe multiple calls to the Full GC. After multiple GCS, the meta-space virtual machine automatically adjusts the high water level to delay the next garbage collection.

6.4. MaxMetaspaceSize tuning

  • -XX:MaxMetaspaceSize={unlimited}
  • The size of the meta space is limited by the amount of memory on your machine
  • Limit the amount of memory used by the metadata of a class to prevent virtual memory switches and local memory allocation failures. This parameter should be used if classloader leaks are suspected; This parameter should also be set on 32-bit machines if the address space is likely to run out.
  • The initial size of the meta space is 21M – this is the initial high water mark for GC, beyond which Full GC is used for class collection.
  • If GC is too frequent after startup, set this value to a larger value
  • It can be set to the same size as the persistent generation to delay GC execution

6.5. Related tools

1. Jmap-clstats PID Prints classloader data. (-clSTATS is an alternative to -permstat, which was used to print classloader data prior to JDK8).

2. Jstat -gc PID displays metadata information

# jstat -gc 1
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU
   CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
3584.0 3584.0 1974.9  0.0   265728.0 98818.8   546304.0   25537.1   53504.0 5165
8.1 6656.0 6380.1     58    0.869   1      0.374    1.243
Copy the code

Where the value of MC, MU is the information of the metaclass. MC: Metaspace Capacity(KB) Current Metaspace Capacity MU: Metaspace Utilization(KB) Metaspace Utilization

3. JCMD PID GC.class_STATS A new diagnostic command that connects to a running JVM and outputs a detailed histogram of class metadata.

6.6. Existing problems

The metaspace virtual machine takes the form of block allocation, and the block size is determined by the class loader type. The class information is not fixed size, so it is possible to allocate a free block of a different size than the block required by the class, in which case fragmentation can exist. Metaspace virtual machines currently do not support compression operations, so fragmentation is currently the biggest problem.