1. JVM memory model

The.java files are compiled into.class files by the compiler, and the JVM class loader loads the bytecode files for each class, which are then executed by the JVM. The Runtime Data Area (Runtime Data Area) is used by the JVM to store Data and information needed during the execution of the program.

1.1. Program counter

A program counter is a line number indicator that records the bytecode executed by the current thread.

The JVM uses a CPU time-slice rotation algorithm to schedule multiple threads. When the suspended thread retrieves the time slice, it must know where it last executed before it can continue, so a program counter is a record of where a thread’s bytecode was executed.

  • Occupies a small memory space.
  • Threads are private, and each thread has a separate program counter.
  • The only one not specified in the JVM specificationOutOfMemoryErrorThe area of the situation.
  • When a Java method is executed, the program counter is valued and records the address of the bytecode instruction being executed.
  • When native methods are executed, the program counter is null (Undefined). Because native method is Java directly calls the native C/C++ library through JNI, since this method is implemented through C/C++, the corresponding bytecode cannot be generated, and the memory allocation during C/C++ execution is determined by the language, not by the JVM.

1.2. Virtual machine stack

www.jianshu.com/p/ecfcc9fb1…

Describes the memory model of Java method execution, used to store stack frames.

  • Thread isolation, where each thread has its own virtual stack.
  • The memory used does not need to be contiguous.
  • The JVM specification allows the virtual machine stack to be implemented as a fixed size (the stack size is determined at thread creation) or to be resized by dynamic expansion and contraction.

1.2.1. The stack frame

A new stack frame is created for each thread calling the same or different method. The more method chains are called, the more stack frames are created (recursively). For each method, the process from invocation to completion corresponds to the process from stack to stack. In the Running thread, all instructions can only operate on the current frame (the frame at the top of the stack).

Stores information such as local variable tables, operand stacks, dynamic connections, method return addresses, additional information, and so on.

  • Local variables table: used to store method parameters and local variables defined within a method.
  • Operand stack: This is where the execution of the method is done. Each bytecode instruction is written to and extracted from the operand stack, which is the process of loading and unloading. The JVM’s JIT engine is called a “stack-based execution engine,” where the “stack” is the operand stack.
  • Dynamic concatenation: Each stack frame contains a reference to a method in the runtime constant pool that the stack frame attributes. This reference is held to support dynamic concatenation during method calls.
  • Method return address: After a method exits, it needs to return to the location where it was called before the program can continue executing. Generally, when a method exits normally, the return address can be the value of the caller’s program counter, which is likely to be stored in the stack frame. When a method exits abnormally, the return address is determined by the exception handler table, which is generally not stored in the stack frame.
  • Additional information: The JVM specification allows specific virtual machine implementations to add information to the stack frame that is not described in the specification, such as debugging information, depending on the specific virtual machine implementation.

1.2.2. Stack memory overflow

Cause stack memory to overflow:

  • Too many stack frames pushed (recursive calls) beyond the maximum capacity allowed by the JVM are thrownStackOverflowError.
  • Thrown if the stack frame is too large (and not likely to occur) to have enough memory to create the corresponding Java virtual machine stackOutOfMemoryError.

1.3. Local method stack

Almost identical to the virtual machine stack, objects are Native methods. Native methods used by virtual machines. Native method stacks are not mandated in the JVM specification and can be implemented freely by different virtual machines. HotSpot VM, for example, combines the local method stack with the Java virtual machine stack.

  • The local method stack is a last-in, first-out stack.
  • Because it’s thread private, the life cycle starts with the thread, starts with the thread, and ends with the thread.
  • The local method stack throwsStackOverflowErrorOutOfMemoryErrorError.

1.4. The heap

The largest memory space, shared by all threads, used to store object instances and array contents. Almost all object instances are stored and allocated in the heap.

  • From the perspective of memory Allocation, Thread Local Allocation Buffers (TLabs) that are private to multiple threads can be allocated to a Java heap shared by threads.
  • The JVM specification states that the Java heap can be physically discontinuous as long as it is logically continuous. It can be fixed size or scalable size. Most VMS are scalable.
  • If the size is expandable, the JVM will throw it if it can’t get enough memory when trying to scaleOutOfMemoryErrorThe exception.

1.5. Methods area

The JVM specification describes the method area as a logical part of the Heap, but it has an alias non-heap to distinguish it from the Java Heap. Method areas are not the same as persistent generations, but because HotSpot VM uses persistent generation to implement method areas, the concept of persistent generation does not exist for other Java virtual machines.

  • The method area, like the Java heap, is an area of memory shared by all threads.
  • Pre-jdk7 (persistent generation) is used to store class information that has been loaded by the virtual machine, constants, static variables, code compiled by the just-in-time compiler, and so on.
  • The runtime constant pool is part of the method area. In addition to the Class version/field/method/interface description information, the Class file contains the constant pool, which is used to store the various literals and symbolic references generated at compile time. This part of the file is stored in the runtime constant pool when the Class is loaded into the method area. It is also possible to put new constants into the pool at run time, a feature that is often used by developersString.intern()Methods. Thrown when the constant pool can no longer be allocated to memory due to method area memory limitationsOutOfMemoryErrorThe exception.
  • Wrapper classes in JavaByte,Short,Integer,Long,Character,BooleanHave implemented constant pool technology,FloatDoubleIs not implemented.Byte,Short,Integer,Long,CharacterThe wrapper classes for these five types of integer are only available when the corresponding values are in- 128 ~ 127Object pools are available only when.

1.5.1. Composition structure

【 JDK 6 】

The permanent generation is physically part of the heap, and the Cenozoic and old age addresses are contiguous.

【 the JDK 8 】

The meta space is local memory.

1.5.2. Method area memory overflow

  • Persistent generation memory runs out prior to JDK 8java.lang.OutOfMemoryError: PermGen space.
  • Persistent generation memory overflow will occur after JDK 8java.lang.OutOfMemoryError: Metaspace.


2. Escape analysis

Escape analysis is an optimization technique in the Java Virtual Machine, but it does not directly optimize code, but rather provides the basis for other optimization methods. JDK8 is enabled by default.

The basic behavior of escape analysis is analyzing object dynamic scope. When an object is defined in a method, it may be referenced by external methods, which is called method escape. It can also be accessed by an external thread, called thread escape.

There are three escape states of an object:

  • GlobalEscape:A reference to an object escapes a method or thread:
    • A reference to an object is assigned to a class variable (a member variable or static member variable).
    • The reference to the object is stored in the escaped object.
    • Object is returned as the return value of the method.
  • Parameter escape ArgEscape: Passes a reference to an object to the calling method during a method call.
  • NoEscape NoEscape: an object that can be scalar replaced without being allocated on the heap.
private Object o;
private HashMap<Integer, Object> map = new HashMap<>();

// Assign a value to a member variable, and global escape occurs
public void test1(a) {
    o = new Object();
}

// Stored in the escaped object, a global escape occurs
public void test2(a) {
    Object o = new Object();
    map.put(0, o);
}

// Global escape occurs as a method return value
public Object test3(a) {
    return new Object();
}

// Instance reference is passed, parameter escape occurs
public void test4(a) {
    Object o = methodPointerEscape();
}

// Pure local scope, no escape
public void test5(a) {
    Object o = new Object();
}
Copy the code

2.1. Scalar substitution

Scalar substitution is the shredding of a Java object and restoring its used member variables to their primitive data types for access, depending on how the program accesses them.

“Scalar”

A single piece of data can no longer be decomposed into smaller pieces of data. Basic Java virtual machine data types such as byte, short, int, long, Boolean, char, float, double, and reference cannot be further decomposed. These are called scalars.

[Amount of polymerization]

A piece of data that can be further decomposed is called an aggregate. The object is the most typical aggregate quantity.

[Replacement process]

If an object does not escape, the runtime may not create the object, but instead directly create its member variables that are used by the method.

After splitting the object, not only can the member variables of the object be allocated and read on the stack (the data stored on the stack is highly likely to be allocated by the virtual machine to be stored in the high-speed register of the physical machine), but also can create the conditions for further optimization.

class User {
    int age;
    int id;
}

public void test(a) {
    // Since the User object does not escape, the User object can be split into two scalars
    // So the User object can be assigned to the stack
    User user = new User();
    // user.id = 1;
}
Copy the code

2.2. Stack allocation

Based on escape analysis and scalar substitution. JDK8 is enabled by default.

[principle]

If the local variable object in a method does not escape, the object is decomposed using a scalar replacement and memory is allocated on the stack, not in the heap, and execution continues on the call stack after allocation.

After the method is executed, it is automatically destroyed. After the thread ends, the stack space is reclaimed and the local variable object is also reclaimed. GC is not required to improve the system performance.

public static void alloc(a) {
    byte[] b = new byte[2];
    b[0] = 1;
}

public static void main(String[] args) {
    // A large number of objects are created and destroyed in the heap in a short time, which leads to frequent GC and memory jitter. The final execution time is around 900ms
    // Memory jitter can be completely avoided by using on-stack allocation, and the final execution time is around 6ms
    for (int i = 0; i < 100000000; i++) { alloc(); }}Copy the code

[Usage Scenario]

For a large number of small scattered objects, allocation on The stack is fast, avoiding The stop-the-world effects of GC. However, the stack space is relatively small, so large objects are not suitable for stack allocation.

2.3. Synchronization elimination

If an object does not escape, synchronization of the variable can be eliminated. There is no lock contention in a single thread. (That is, the lock and the object in the lock block can not escape the thread, can cancel the synchronization block)

public static void alloc(a) {
    byte[] b = new byte[2];
    // No thread escapes, so the synchronization lock can be removed
    // Enable synchronization elimination execution time about 10 ms
    // Close the execution time using synchronization elimination around 3870 ms
    synchronized (b) {
         b[0] = 1; }}public static void main(String[] args) {
    for (int i = 0; i < 100000000; i++) { alloc(); }}Copy the code