Test: OutOfMemoryError exception

Keywords: Java JDK jvm Windows

Catalog

1, purpose

2. Practice (jdk8)

2.1 Java heap overflow

2.2 virtual machine stack and local method stack overflow

2.3 method area and runtime constant pool overflow

2.4 local direct memory overflow

Reference resources

1, purpose

Verify the contents stored in each runtime area described in the Java virtual machine specification by code;

When encountering the actual memory overflow exception, we can quickly know which area is the memory overflow according to the prompt information of the exception, know what kind of code may cause the memory overflow of these areas, and how to deal with these exceptions.

2. Practice (jdk8)

2.1 Java heap overflow

1) Idea: Java heap is used to store object instances. As long as the objects are created continuously and there is a reachable path between GC Roots and objects to avoid garbage collection mechanism clearing these objects, with the increase of the number of objects, the total capacity will touch the maximum heap capacity limit, and then memory overflow exception will occur.

2) Code: Java heap memory overflow exception test

public class HeapOom {
    static class OomObject {}

    public static void main(String[] args) {
        List<OomObject> list = new ArrayList<OomObject>();
        while (true) {
            list.add(new OomObject());
        }
    }
}

3)Run/Debug Configurations

VM options:

-verbose:gc    //Output GC status in the console
-Xms20M        //JVM initial memory size
-Xmx20M        //JVM maximum memory size  
-Xmn10M        //Memory size of younger generation
-XX:+PrintGCDetails  //Print GC details
-XX:SurvivorRatio=8  //Set the size ratio of Eden area and Survivor area to 8 in the young generation
-XX:+HeapDumpOnOutOfMemoryError  //Generate Dump files automatically when the JVM has an OOM
-XX:HeapDumpPath=E:\jvmDump      //Set Dump file path

4) results

5) description

The OutOfMemoryError exception of Java heap memory is the most common memory overflow exception in practical application. In case of Java heap memory overflow, the exception stack information "java.lang.OutOfMemoryError" will follow the further prompt "Java heap space".

6) solve

To solve the exception of this memory area, the normal processing method is to analyze the Dump snapshot from Dump through memory image analysis tool (such as Eclipse Memory Analyzer). The first step is to confirm whether the object causing OOM in memory is necessary, that is, to distinguish whether Memory Leak or memory overflow occurs.

If it is a memory leak, you can further view the reference chain from the leak object to the GC Roots through the tool to find out what reference path the leak object is through and which GC Roots it is associated with, so that the garbage collector cannot recycle them. Generally, according to the type information of the leak object and its information to the GC Roots reference chain, you can locate these objects more accurately Location, and then find out the specific location of the code that generated the memory leak.

If it's not memory leak, in other words, all objects in memory must survive, then you should check the Java virtual machine's heap parameter (- Xmx and - Xms) settings, and compare them with the machine's memory to see if there is still room for upward adjustment. Then check whether there are some objects with too long life cycle, too long holding state time, unreasonable storage structure design, etc. from the code, to minimize the memory consumption during the running period of the program.

2.2 virtual machine stack and local method stack overflow

1) description

There is no distinction between the virtual machine stack and the local method stack in the HotSpot virtual machine. Therefore, for HotSpot, the - Xoss parameter (set the local method stack size) exists, but actually has no effect. The stack capacity can only be set by the - Xss parameter.

As for the virtual machine stack and the local method stack, two exceptions are described in the Java virtual machine specification:

  • 1) If the stack depth requested by a thread is greater than the maximum depth allowed by the virtual machine, a StackOverflowError exception will be thrown.
  • 2) If the stack memory of the virtual machine allows dynamic expansion, an OutOfMemoryError exception will be thrown when the extended stack capacity cannot be applied to enough memory.

The Java virtual machine specification explicitly allows the Java virtual machine to choose whether to support the dynamic expansion of the stack or not, while the HotSpot virtual machine does not support the expansion. Therefore, unless the OutOfMemoryError exception occurs when the thread is created to apply for memory because it cannot obtain enough memory, the memory overflow will not occur when the thread is running, only because of the stack capacity Unable to hold new stack frame causing StackOverflowError exception.

2) Code (single thread)

Experiment 1: use the - Xss parameter to reduce the stack memory capacity (for different versions of Java virtual machines and different operating systems, the minimum stack capacity may be limited, which mainly depends on the operation
Make paging size of system memory.)

// Using the - Xss parameter to reduce stack memory capacity
public class JavaVMStackSOF {
    private int stackLength = 1;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length: " + oom.stackLength);
            throw e;
        }
    }
}

/*
Result:
Throw the StackOverflowError exception. When the exception occurs, the stack depth of the output will be reduced accordingly.

Exception in thread "main" stack length: 980
java.lang.StackOverflowError
	at com.wyq.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:73)
	at com.wyq.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:74)
    ......
*/

Experiment 2: a large number of local variables are defined to increase the length of local variable table in this method frame.

public class JavaVMStackSOF {
    private static int stackLength = 0;

    public static void test() {
        long unused1, unused2, unused3, unused4, unused5,
                unused6, unused7, unused8, unused9, unused10,
                unused11, unused12, unused13, unused14, unused15,
                unused16, unused17, unused18, unused19, unused20,
                unused21, unused22, unused23, unused24, unused25,
                unused26, unused27, unused28, unused29, unused30,
                unused31, unused32, unused33, unused34, unused35,
                unused36, unused37, unused38, unused39, unused40,
                unused41, unused42, unused43, unused44, unused45,
                unused46, unused47, unused48, unused49, unused50,
                unused51, unused52, unused53, unused54, unused55,
                unused56, unused57, unused58, unused59, unused60,
                unused61, unused62, unused63, unused64, unused65,
                unused66, unused67, unused68, unused69, unused70,
                unused71, unused72, unused73, unused74, unused75,
                unused76, unused77, unused78, unused79, unused80,
                unused81, unused82, unused83, unused84, unused85,
                unused86, unused87, unused88, unused89, unused90,
                unused91, unused92, unused93, unused94, unused95,
                unused96, unused97, unused98, unused99, unused100;
        stackLength++;
        test();

        unused1 = unused2 = unused3 = unused4 = unused5 =
        unused6 = unused7 = unused8 = unused9 = unused10 =
        unused11 = unused12 = unused13 = unused14 = unused15 =
        unused16 = unused17 = unused18 = unused19 = unused20 =
        unused21 = unused22 = unused23 = unused24 = unused25 =
        unused26 = unused27 = unused28 = unused29 = unused30 =
        unused31 = unused32 = unused33 = unused34 = unused35 =
        unused36 = unused37 = unused38 = unused39 = unused40 =
        unused41 = unused42 = unused43 = unused44 = unused45 =
        unused46 = unused47 = unused48 = unused49 = unused50 =                                                                                    
        unused51 = unused52 = unused53 = unused54 = unused55 =                                                                                               
        unused56 = unused57 = unused58 = unused59 = unused60 =                                                                                                       
        unused61 = unused62 = unused63 = unused64 = unused65 =                                                                                                            
        unused66 = unused67 = unused68 = unused69 = unused70 =                                                                                                                        
        unused71 = unused72 = unused73 = unused74 = unused75 =                                                                                                                               
        unused76 = unused77 = unused78 = unused79 = unused80 =                                                                                                                                
        unused81 = unused82 = unused83 = unused84 = unused85 =                                                                                                                                                
        unused86 = unused87 = unused88 = unused89 = unused90 =                                                                                                                                                        
        unused91 = unused92 = unused93 = unused94 = unused95 =                                                                                                                                                                
        unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }
    public static void main(String[] args) {
        try {
            test();
        }catch (Error e){
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

/*
Result:
Throw the StackOverflowError exception. When the exception occurs, the stack depth of the output will be reduced accordingly.

stack length:3489
Exception in thread "main" java.lang.StackOverflowError
	at com.wyq.JavaVMStackSOF.test(JavaVMStackSOF.java:34)
	at com.wyq.JavaVMStackSOF.test(JavaVMStackSOF.java:34)
    ......
*/

3) conclusion

Whether the stack frame is too large or the virtual machine stack capacity is too small, when the new stack frame memory cannot be allocated, the HotSpot virtual machine throws the StackOverflowError exception. However, on virtual machines that allow dynamic expansion of stack capacity, the same code will lead to different situations. The same code successfully generated an OutOfMemoryError instead of a stackovover flowerror exception in the Classic virtual machine.

4) expand

If the test is not limited to single thread, memory overflow exception can also be generated on HotSpot by continuously establishing thread,. However, there is no direct relationship between the memory overflow exception and the stack space, which mainly depends on the memory usage of the operating system itself. It can even be said that in this case, the larger the memory allocated to each thread's stack, the more likely it is to generate a memory overflow exception.

Reason: the memory allocated to each process by the operating system is limited. For example, the maximum memory of a single process of 32-bit windows is limited to 2GB. The HotSpot virtual machine provides parameters to control the maximum memory of Java heap and method area. The remaining memory is 2GB (operating system limit) minus the maximum heap capacity, and then minus the maximum method area capacity. Because the program counter consumes very little memory, it can be ignored. If the direct memory and the memory consumed by the virtual machine process itself are also removed, the remaining memory is The next memory is allocated by the virtual machine stack and the local method stack. Therefore, the larger the stack memory allocated to each thread, the fewer threads can be established naturally, and the easier it is to run out of remaining memory when establishing threads. (the corresponding code will not be tested here, because in the virtual machine of Windows platform, the java thread is mapped to the kernel thread of the operating system, unlimited thread creation will bring great pressure to the operating system.)

2.3 method area and runtime constant pool overflow

1) description

Since JDK 7, the string constant pool originally stored in the permanent generation has been moved to the Java heap, so in JDK 7 and above, it is meaningless to limit the capacity of the method area for this test case. At this time, use the - Xmx parameter to limit the maximum heap to 6MB to see the memory overflow error:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

2) code

public class RuntimeConstantPoolOom {
    public static void main(String[] args) {
        // Use Set to keep constant pool reference and avoid Full GC reclaiming constant pool behavior
        Set<String> set = new HashSet<String>();
        // In the short range, it's enough for 6MB of PermSize to generate OM
        short i = 0;
        while (true)  {
            set.add(String.valueOf(i++).intern());
        }
    }
}

/*
Operation result:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.HashMap.resize(HashMap.java:704)
	at java.util.HashMap.putVal(HashMap.java:663)
	at java.util.HashMap.put(HashMap.java:612)
	at java.util.HashSet.add(HashSet.java:220)
	at com.wyq.RuntimeConstantPoolOom.main(RuntimeConstantPoolOom.java:28)
*/

3) conclusion

From the run result, we can see that when the run-time constant pool overflows, the prompt message following the OutOfMemoryError exception is "Java heap space", indicating that the run-time constant pool indeed belongs to a part of the heap.

4) expansion

There are some more interesting implications for the implementation of this string constant pool:

public class RuntimeConstantPoolOom {
    public static void main(String[] args) {
        String str1 = new StringBuilder("Computer").append("Software").toString();
        System.out.println(str1.intern() == str1);

        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);
    }
}

/*
jdk8 The following operation results are:
true
false
*/

When this code is run in JDK 6, it will get two false, while when it is run in > = JDK 7, it will get one true and one false. The reason for the difference is that in JDK 6, the inter() method will copy the first encountered string instance to the string constant pool of the permanent generation for storage, and return the reference of the string instance in the permanent generation. The string object instance created by StringBuilder is on the Java heap, so it is impossible to be the same reference, and the result will return false.

And >=JDK 7 (and some other virtual machines, such as JRockit) do not need to copy the string instance to the permanent generation. Since the string constant pool has been moved to the Java heap, you only need to record the first instance reference in the constant pool. Therefore, the reference returned by Intern () and the string instance created by StringBuilder are the same . The str2 comparison returns false, because the string "Java" appeared before executing string builder. Tostring(), and the string constant pool already has its reference, which does not conform to the principle of "first encounter" required by the method of intern(), and the string "computer software" appears for the first time, so the result returns true.

5) Memory overflow exception in method area with CGLib

public class JavaMethodAreaOom {
    static class OomObject {}

    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 o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invoke(o, objects);
                }
            });
            enhancer.create();
        }
    }
}

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
	at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:348)
	at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
    ......

After JDK 8, the permanent generation completely withdrew from the stage of history, and meta space came on stage as its substitute.

-20: Maxmetaspacesize: sets the maximum value of the Metaspace, which is - 1 by default, that is, unlimited, or only limited by the local memory size.
-20: Metaspacesize: Specifies the initial space size of the Metaspace, in bytes. Reaching this value will trigger garbage collection for type
 Unload, and the collector will adjust the value: if a large amount of space is released, reduce the value appropriately; if a small amount of space is released,
Then increase the value appropriately if it does not exceed - XX:MaxMetaspaceSize (if set).
-20: Minmetaspacefreeratio: it is used to control the percentage of the minimum remaining capacity of the Metaspace after garbage collection, which can reduce the
 Frequency of garbage collection due to insufficient meta space. Similarly, the - XX: Max metaspacefreeratio is used to control the maximum remaining Metaspace
 Percentage of excess capacity.

2.4 local direct memory overflow

Direct Memory Memory) can be specified by the - XX:MaxDirectMemorySize parameter. If it is not specified, the default value is the same as the Java heap maximum value (specified by - Xmx). The following code directly obtains the Unsafe instance through reflection over the DirectByteBuffer class for memory allocation (the getUnsafe() method of the Unsafe class specifies that only the boot class loader will return the instance, which reflects the designer's hope It is hoped that only the classes in the virtual machine standard class library can use the Unsafe function. In JDK Some functions of Unsafe will be opened to external use through VarHandle at 10:00). Although using DirectByteBuffer to allocate memory will throw memory overflow exception, when it throws an exception, it does not really apply to the operating system for allocating memory, but it will manually throw overflow exception in the code when it knows that the memory cannot be allocated through calculation. The real way to apply for allocating memory is u nsafe::allocateMemory().

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);
        }
    }
}

/*
Exception in thread "main" java.lang.OutOfMemoryError
	at sun.misc.Unsafe.allocateMemory(Native Method)
	at com.wyq.DirectMemoryOom.main(DirectMemoryOom.java:22)
*/

An obvious feature of memory overflow caused by direct memory is that in heap There is no obvious exception in the Dump file. If the reader finds that the Dump file generated after the memory overflow is very small, and DirectMemory is used directly or indirectly in the program (the typical indirect use is NIO), then the reason for direct memory can be considered.

Reference resources

Deep understanding of Java virtual machines: JVM advanced features and best practices (Third Edition)

Published 95 original articles, won praise 16, visited 50000+
Private letter follow

Posted by ahmedkl on Tue, 28 Jan 2020 20:04:03 -0800