In addition to program counters, several other run-time areas of virtual machine memory are subject to OutOfMemoryError (OOM) exceptions as specified in the Java Virtual Machine Specification. (This paper is mainly based on JDK1.8)
The Java heap overflow
The Java heap is used to store object instances, and as long as we keep creating objects and making sure that there is a reachable path between GC Roots and objects to avoid garbage collection cleaning them up, as the number of objects increases and the total capacity hits the maximum heap capacity limit, there will be a memory overflow exception.
Simulation code
Here is a simple code to simulate a heap overflow:
/ * * * Args VM: - Xms10m - Xmx10m - XX: + HeapDumpOnOutOfMemoryError *@author xx
* @dateThe 2021-8-13 * /
public class HeapOOM {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[2048]); }}}Copy the code
The following information is displayed:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at cn.xx.jvm.oom.HeapOOM.main(HeapOOM.java:16)
Copy the code
Problem analysis
We need to locate a Memory Leak or a Memory Overflow.
- A memory leak
- Out of memory
A memory leak
We can use the jVisualVM tool that comes with the JDK to load the heap snapshot file for analysis.
If it is a memory leak, you can further check the reference chain of the leaking object to THE GC Roots through the tool to find out what reference path the leaking object passed through and which GC Roots it was associated with that caused the garbage collector to fail to collect them. Based on the information about the type of the leaking object and its information about the GC Roots reference chain, It is generally possible to pinpoint the location where these objects were created, thus finding the specific location of the code that produced the memory leak.
Out of memory
If it is not a memory leak, in other words, objects in memory really must live, then you should check the Heap parameters (-xmx versus -xMS) Settings of the Java virtual machine against the machine’s memory to see if there is room for upward adjustment. Then check from the code whether there are some object life cycle is too long, hold the state time is too long, the storage structure design is not reasonable and so on, as far as possible to reduce the memory consumption during the running of the program.
The VM stack and local method stack overflow
There is no distinction between the virtual stack and the local method stack in the HotSpot virtual machine, so the -xoss parameter (which sets the size of the local method stack) exists for HotSpot, but it has virtually no effect, and the stack size can only be set by the -xss parameter. There are two types of exceptions described in the Java Virtual Machine Specification regarding virtual machine stacks and local method stacks:
- If a thread requests a stack depth greater than the maximum allowed by the virtual machine, a StackOverflowError exception is thrown.
- If the stack memory of a VIRTUAL machine allows dynamic expansion, an OutOfMemoryError will be thrown when sufficient memory cannot be obtained for the extended stack.
The Java VIRTUAL Machine specification explicitly allows the Java VIRTUAL machine implementation to choose whether or not to support dynamic stack extension, while the HotSpot virtual machine chooses not to support extensions, so unless there is an OutOfMemoryError when creating a thread to apply for memory because it cannot get enough memory, Otherwise, while the thread is running, there will be no memory overflow due to the extension, only a StackOverflowError will occur because the stack cannot accommodate the new stack frame.
The MEMORY of the VM stack overflows
StackOverflowError
Example code:
/**
* VM Args:-Xss128k
*
* @author xx
* @dateThe 2021-08-13 * /
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak(a) {
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);
throwe; }}}Copy the code
Return abnormal information
Exception in thread "main" java.lang.StackOverflowError stack length:992 at cn.xx.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13) at cn.xx.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14) //.... Omit the moreCopy the code
OutOfMemoryError
package cn.xx.jvm.oom;
/ * * *@author xx
* @dateThe 2021-08-13 * /
public class JavaVMStackSOF2 {
private static int stackLength = 0;
public static void test(a) {
longunused1, 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);
throwe; }}}Copy the code
Output results:
stack length:6986
Exception in thread "main" java.lang.StackOverflowError
at cn.xx.jvm.oom.JavaVMStackSOF2.test(JavaVMStackSOF2.java:22)
at cn.xx.jvm.oom.JavaVMStackSOF2.test(JavaVMStackSOF2.java:22)
Copy the code
conclusion
Whether because the stack frame is too large or the virtual machine stack is too small, the HotSpot virtual machine will always throw a StackOverflowError exception when new stack frame memory cannot be allocated. However, on a virtual machine that allows dynamic stack expansion, the same code will cause a different situation.
Memory overflow caused by thread creation
Note: The following experiment may cause the operating system to stall. It is recommended to perform it on a virtual machine
/**
* VM Args:-Xss512k
*
* @author xx
* @dateThe 2021-08-13 * /
public class JavaVMStackOOM {
private void dontStop(a) {
while (true) {}}public void stackLeakByThread(a) {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run(a) { dontStop(); }}); thread.start(); }}public static void main(String[] args) throws Throwable {
JavaVMStackOOM oom = newJavaVMStackOOM(); oom.stackLeakByThread(); }}Copy the code
Method area and runtime constant pool overflow
Since the runtime constant pool is part of the method area, overflow tests for both areas can be performed together. HotSpot has a gradual “deperpetuation” plan starting with JDK 7, and in JDK 8 fully uses meta space to replace perpetuation.
Method area memory overflow
The main responsibility of the method area is to hold information about the type, such as class name, access modifiers, constant pool, field description, method description, and so on. The basic idea for testing this area is that the runtime generates a large number of classes to fill the method area until it overflows. While directly using the Java SE API can be dynamically generated classes (such as reflection GeneratedConstructorAccessor and dynamic agent, etc.), but in this experiment with the aid of additional direct operation generated a lot of dynamic class bytecode runtime.
/** * VM Args: -xx :MetaspaceSize=21m -xx :MaxMetaspaceSize=21m **@author xx
* @dateThe 2021-08-13 * /
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(); }}static class OOMObject {}}Copy the code
Output code
Caused by: java.lang.OutOfMemoryError: Metaspace
Caused by: java.lang.OutOfMemoryError: Metaspace
Copy the code
Constant pool case
String::intern() is a local method that returns a reference to a String representing the String in the pool if the pool of String constants already contains a String equal to that String. Otherwise, the String contained in this String is added to the constant pool and a reference to this String is returned. In the HotSpot virtual machine of JDK 6 or earlier, constant pools are allocated in the permanent generation, and we can indirectly limit the capacity of the constant pool by limiting the size of the permanent generation by -xx: PermSize and -xx: MaxPermSize.
/ * * *@author xx
* @dateThe 2021-08-13 * /
public class RuntimeConstantPoolOOM2 {
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); }}Copy the code
If you run this code in JDK 6, you get two false’s, and if you run it in JDK 7, you get one true and one false. The reason for the difference is that in JDK 6, the first encountered string instance is copied into the permanent generation’s string constant pool, returning a reference to the string instance in the permanent generation, while the string object instance created by the StringBuilder is on the Java heap. So it must not be the same reference, and the result will return false.
The implementation of the intern() method in JDK 7 (and some other virtual machines, such as JRockit) does not need to copy the string instances to the permanent generation. Since the string constant pool has been moved to the Java heap, it simply records the first instance reference in the constant pool. So the reference that intern() returns is the same as the string instance that was created by the StringBuilder. The String “Java” was already present before string-Builer.tostring () was executed, and there is already a reference to it in the String constant pool. This does not comply with the “first encounter” rule that the intern() method requires. The string “computer software” appears for the first time, so the result returns true.
Native direct memory overflow
The size of DirectMemory can be specified by the -xx :MaxDirectMemorySize parameter. By default, it is the same as the maximum Java heap size (specified by -xmx). The getUnsafe() method of the Unsafe class specifies that only the bootstrap class loader will return the instance, reflecting on the Unsafe class. The designer wanted only classes in the standard virtual machine library to use the Unsafe feature. Even though some of the functionality of Unsafe was opened to outsiders in JDK 10 via VarHandle), DirectByteBuffer does throw an overflow exception when allocating memory, but it doesn’t actually ask the operating system to allocate memory. Instead, it calculates that memory cannot be allocated and throws an overflow exception in its code. The only way to actually allocateMemory is through Unsafe::allocateMemory().
/** * VM Args: -xmx20m -xx :MaxDirectMemorySize=10M **@author xx
* @dateThe 2021-08-13 * /
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
Output content:
Exception in thread "main" java.lang.OutOfMemoryError
Exception in thread "main" java.lang.OutOfMemoryError
at java.base/jdk.internal.misc.Unsafe.allocateMemory(Unsafe.java:616)
at jdk.unsupported/sun.misc.Unsafe.allocateMemory(Unsafe.java:462)
at cn.xx.jvm.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:21)
Copy the code
The resources
- Deeper understanding of the JVM Virtual Machine – 3rd edition. By Zhiming Zhou
- Docs.oracle.com/javase/spec…