preface

JVM memory areas include PC counters, Java virtual machine stacks, local method stacks, heaps, method areas, runtime constant pools, and direct memory.

This paper mainly introduces the functions and characteristics of each memory region, as well as the possibility and exception types of memory overflow in each region.

The body of the

(I). JVM memory area

When a Java VM executes Java programs, it divides the managed memory into different data areas. Each of these memory areas has its own purpose and creation and destruction time. Some regions exist with the start of a virtual machine process, and some regions are created and destroyed with the start and end of a user thread.

The JVM memory region is also called the Java runtime data region. These include: program counters, virtual stack, local method stack, heap, static method area, static constant pool, and so on.

Note: program counters, virtual machine stacks, and local method stacks are private to each thread; The heap and method areas are shared by threads.

1.1. PC counter

The Program Counter Register is a small memory space that acts as an indicator of the bytecode line number executed by the current thread.

  1. An indicator of the bytecode line number executed by the current thread.
  2. Each thread has its ownPCCounter.
  3. threadprivate, the life cycle is the same as the thread, along withJVMStart to be born,JVMShut down and die.
  4. Threads executeJavaMethod, record the virtual machine it is executingAddress of bytecode instruction.
  5. Threads executeNativeMethod, the counter is recorded asempty(Undefined).
  6. The only onJavaThe virtual machine specification does not specify anyOutOfMemoryErrorSituation area.

1.2. Java Virtual machine stack

Thread private memory space, which has the same life cycle as a thread. During thread execution, a Stack Frame is created for each method execution, which is used to store information about local variables, operand stacks, dynamic links, method exits, and so on.

  1. Local variable scale
  2. The operand stack
  3. Dynamic link
  4. Methods the export

The process of each method from invocation to completion corresponds to the whole process of loading and unloading a stack frame in the virtual machine stack.

The concrete structure and function of the four components in the stack frame are explained as follows:

1). Local variation scale

A local variable table is a storage space for a set of variable values used to store method parameters and local variables. Max_locals in the Code property of the method table of the Class file specifies the maximum size of the local variable table required by the method.

The local variable table allocates memory space at compile time and can hold various variable types at compile time:

  1. Basic data typesboolean.byte.char.short.int.float.long.doubleEtc.8Kind of;
  2. Object reference typereference, pointing to an objectThe starting addresstheReference to a pointer;
  3. Return address typereturnAddress, returns the type of the address.

Variable Slot:

A variable slot is the smallest unit of a local variable scale, defined as 32 bits. For 64-bit long and double variables, the virtual machine allocates two consecutive Slot Spaces.

2) operand stack

An Operand Stack, also known as an operation Stack, is a last-in, first-out Stack. Max_stacks in the Code attribute of the Class file specifies the maximum stack depth during execution. The interpreted execution engine of the Java virtual machine is called a stack-based execution engine, where the stack refers to the -operand stack.

  1. andLocal variable scaleThe same,The operand stackIs also an example of32Word lengthIs an array of units.
  2. The virtual machine is stored in the operand stackThe data type:int,long,float,double,referenceandreturnTypeAnd other types (forbyte,shortAs well ascharValues of type are also converted to before being pushed onto the operand stackint).
  3. Unlike a local variable table, it is accessed not through an index, but through the standard stack operations – pushing and unloading. For example, if an instruction pushes a value onto the operand stack, another instruction can pop that value up for use later.

The virtual machine uses the operand stack as its workspace — most instructions pop data from here, perform operations, and then push the results back into the operand stack.

begin
iload_0    // push the int in local variable 0 onto the stack
iload_1    // push the int in local variable 1 onto the stack
iadd       // pop two ints, add them, push result
istore_2   // pop int, store into local variable 2
end
Copy the code

In this bytecode sequence, the first two instructions iloAD_0 and ILoAD_1 push the integers indexed 0 and 1 stored in the local table into the operand stack, and the iADD instruction adds the two integers from the operand stack and pushes the result into the operand stack. The fourth instruction, istore_2, pops the result from the operand stack and stores it at the local variable table index 2.

The following figure details the state changes of the local variable table and operand stack during this process (areas of the local variable table and operand stack that are not used in the figure are represented by blank Spaces).

3). Dynamic linking

Each stack frame contains a reference to a method that belongs in the runtime constant pool and is held to support dynamic linking during method calls.

There are a lot of symbolic references 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. These symbols quote:

  1. Static resolution: part will be inClass loading phaseOr the first time you use itDirect reference(e.g.,final,staticDomain, etc.), calledStatic analysis.
  2. Dynamic parsing: The other part is converted to a direct reference during each run, called dynamic linking.
4). Method returns the address

When a method starts executing, there are only two ways to exit the current method:

  1. Return to normalWhen an execution encounters a return instruction, it passes the return value to the upper method caller. This exit is calledNormal exit completion(Normal Method Invocation Completion), generally, the caller’sPCcounterCan be used as a return address.
  2. Abnormal return: When an exception is encountered and the current method body is not handled, the method will exit, and there is no return value, calledAbnormal completion exit(Abrupt Method Invocation Completion), the return address to be passedException handlerTable to determine.

When a method returns, it may do three things in sequence:

  1. Restores the local variable table and operand stack of the upper method.
  2. The operand stack that pushes the return value into the caller’s stack frame.
  3. willPCcounterThe value of the pointThe next articleMethod instruction position.

Summary:

Note: In the Java Virtual Machine specification, two exceptions are specified for this area. StackOverflowError is raised if the stack depth requested by the current thread is greater than the virtual machine stack allows (if the virtual machine stack does not allow dynamic scaling). Second, an OutOfMemoryError is thrown if sufficient memory cannot be allocated during expansion.

1.3. Local method stack

The role played by the Native method stack and the Java Virtual machine stack is very similar, with the main difference being that the Java virtual machine stack executes Java method services, while the Native method stack executes Native method services (usually written in C).

Some virtual machine distributions, such as the Sun HotSpot VIRTUAL Machine, directly merge the local method stack with the Java virtual machine stack. Like the virtual stack, the local method stack throws StackOverflowError and OutOfMemoryError exceptions.

1.4. The heap

The Java heap is the largest area of memory shared by all threads and is created when the virtual machine is started. The sole purpose of this memory area is to hold object instances, and almost all object instances are allocated memory here.

In Java, the heap is divided into two distinct regions: Young Generation and Old Generation. The New generation (Young) is divided into three zones: one Eden zone and two Survivor zones – From Survivor zone and To Survivor zone.

Brief summary: New object allocation is first placed in Eden area of the Young Generation, Survivor area serves as a buffer between Eden area and Old area. If the object in Survivor area survives several collections, it will be transferred to Old area.

The purpose of this partition is to enable the JVM to better manage objects in heap memory, including memory allocation and reclamation.

1.5. Methods area

The method area, like the Java heap, is shared by multiple threads and is used to store data such as class information, constants, static constants, and just-in-time compiled code.

1.6. Runtime constant pools

The runtime constant pool is part of the method area. In addition to the description of the Class version, fields, methods, and interfaces, a Class file contains a constant pool of information used to store various literal and symbolic references generated during compilation.

1.7. Direct memory

Direct memory is not part of the virtual machine run-time data area, nor is it a memory area defined in the Java Virtual Machine specification. Java NIO allows Java programs to access direct memory, which is usually faster than Java heap memory. Therefore, direct memory is recommended for scenarios with frequent read and write operations and high performance requirements.

(2). Common memory overflow exceptions

In addition to program counters, OutOfMemoryError can occur in any other runtime area of the Java virtual machine, as verified below:

2.1. Java heap overflow

The Java heap can store object instances. Avoid garbage collection by constantly creating objects and ensuring that GC Roots have a reachable path to them. An OutOfMemoryError is raised when the number of objects reaches the maximum heap capacity limit.

Set the JVM startup parameters: -xMS20m sets the minimum heap memory to 20M, and -xmx20m sets the maximum heap memory to be the same as the minimum. This prevents the Java heap from automatically expanding when it runs out of memory. – XX: + HeapDumpOnOutOfMemoryError parameters can make the virtual machine in the event of a memory exceptions when the snapshot Dump out the memory heap runtime.

HeapOOM.java

/** * VM Args: -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError */
public class HeapOOM {
    public static class OOMObject {}public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true) {
            list.add(newOOMObject()); }}}Copy the code

Test results:

Open the Dump file for the Java VisualVM export Heap memory runtime.

HeapOOM
99%
Garbage collector

Analysis: In this case, there are usually two possibilities to consider: memory leak and memory overflow.

  • If it is memory leak:

Further analysis was performed using the Java VisualVM tool to see how the leak object was associated with GC Roots so that the garbage collector could not collect it.

  • If it is memory overflow:

As analyzed by the Java VisualVM tool, there are no leaking objects, that is, objects in heap memory must survive. Consider the following measures:

  1. Check the code to see if there are some objects with too long life cycle and too long duration, and try to reduce the memory of the program runtime.
  2. Check the virtual machineHeap parameters(-Xmxwith-Xms), compared to the machinePhysical memoryLet’s see if we can turn it up.

2.2. Virtual machine and local method stack overflow

In terms of vm stacks and local method stacks, there are two possible memory exception types:

  • If the scene is requestedThe stack depthThe value is greater than the value allowed by the VIRTUAL machineMaximum depthThat will be thrownStackOverflowErrorThe exception.
  • If the VM is inExtend the stackCannot apply for enoughmemorySpace, might throwOutOfMemoryErrorThe exception.

The problem can be divided into two types: when the stack space cannot be allocated, whether the stack memory is too small or the used stack memory is too large.

Abnormal StackOverflowError

Test Plan 1:

  • use-XssParameters to reduceStack memoryThe capacity to print when an exception occursThe stackThe depth.
  • Define a large number of local variables to increase the length of the local variable table in the stack frame.

-xss128K Sets the stack memory size to 128K.

JavaVMStackSOF.java

/** * VM Args: -Xss128k */
public class JavaVMStackSOF {
    private int stackLength = 1;

    private void stackLeak(a) {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("Stack length: " + oom.stackLength);
            throwe; }}}Copy the code

Test results:

In a single thread, a StackOverflowError is raised when memory cannot be allocated, whether the stack frame is too large or the stack size is too small.

Test Plan 2:

  • Keep creating threads and keeping them running.

JavaVMStackOOM.java

/** * VM Args: -Xss2M */
public class JavaVMStackOOM {
    private void running(a) {
        while (true) {}}public void stackLeakByThread(a) {
        while (true) {
            new Thread(new Runnable() {
                @Override
                public void run(a) { running(); } }).start(); }}public static void main(String[] args) {
        JavaVMStackOOM oom = newJavaVMStackOOM(); oom.stackLeakByThread(); }}Copy the code

Test results:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
Copy the code

When the above test code runs, there is a large risk that may cause the operating system to suspend operation, so I will not test it myself here, citing the author’s test results.

2.3. Method area and runtime constant pool overflow

(I). Constant pool memory overflow test at runtime

Runtime constants and literals are stored in the runtime constant pool, which is part of the method area, so the tests for both areas are the same. Here we use string.Intern () to test:

String. Intern () is a native method that returns a String in the String constant pool if it exists. Otherwise, the String contained in the String is put into the constant pool and a reference to the String is returned.

Set JVM startup parameters: -xx :PermSize=10M and -xx :MaxPermSize=10M limit the size of the method area to 10M, indirectly limiting the capacity of the constant pool.

RuntimeConstantPoolOOM.java

/** * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M */
public class RuntimeConstantPoolOOM {

    public static void main(String[] args) {
        // Use List to keep references to the constant pool from being reclaimed by the Full GC
        List<String> list = new ArrayList<>();
        // 10MB PermSize is sufficient to generate OOM in the Integer range
        int i = 0;
        while (true) { list.add(String.valueOf(i++).intern()); }}}Copy the code

Analysis of test results:

JDK1.6 running result:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
Copy the code

The JDK1.6 runtime results show that the constant pool overflows and throws outofMemoryErrors with permanent strings. JDK1.7 and above will not get the same results, and it will loop on and on.

(2) method area memory overflow test

The method area holds class-related information, such as Class names, access modifiers, constant pools, field descriptions, method descriptions, and so on. For method area memory overflow testing, the basic idea is to generate a large number of byte-like areas to fill the method area at run time.

The bytecode technology of Spring framework’s CGLib dynamic proxy is introduced here, and new proxy classes are generated continuously through the loop to achieve the effect of overflow of method area memory.

JavaMethodAreaOOM.java

/** * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M */
public class JavaMethodAreaOOM {

    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    returnproxy.invokeSuper(obj, args); }}); enhancer.create(); }}private static class OOMObject {
        public OOMObject(a) {}}}Copy the code

JDK1.6 running result:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
Copy the code

Analysis of test results:

The JDK1.6 runtime results show that the constant pool overflows and throws outofMemoryErrors with permanent strings. JDK1.7 and above will not get the same results, and it will loop on and on.

2.4. Direct memory overflow

The size of native direct memory can be specified with -xx :MaxDirectMemorySize, or if not specified, defaults to the maximum Java heap size (specified by -xmx).

Test scenario:

Retrieving an Unsafe instance directly uses reflection to request memory allocation from the operating system:

Set the JVM startup parameters: -xmx20m specifies the maximum memory of the Java heap, and -xx :MaxDirectMemorySize=10M specifies the size of direct memory.

DirectMemoryOOM.java

/** * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M */
public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) { unsafe.allocateMemory(_1MB); }}}Copy the code

Test results:

Analysis of test results:

An obvious feature of a memory overflow caused by DirectMemory is that no obvious exception information is seen in the Heap Dump file. If the Dump file is small after OOM and NIO is used directly or indirectly, consider this.


Welcome to pay attention to the technical public number: Zero one Technology Stack

This account will continue to share learning materials and articles on back-end technologies, including virtual machine basics, multithreaded programming, high-performance frameworks, asynchronous, caching and messaging middleware, distributed and microservices, architecture learning and progression.