Wechat official account: Operation and maintenance development story, author: Lao Zheng
In addition to the program counter, several other runtime areas of virtual machine memory are subject to OutOfMemoryError (OOM) exceptions as defined in the Java Virtual Machine Specification. (This article 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 the GC Roots and the objects to avoid the garbage collection mechanism to clean them up, we will run out of memory exceptions as the number of objects increases and the total capacity reaches the maximum heap capacity limit.
Simulation code
Here is a simple code to simulate a heap overflow:
/ * * * Args VM: - Xms10m - Xmx10m - XX: + HeapDumpOnOutOfMemoryError *@author zhengsh
* @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.zhengsh.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 JDK built-injvisualvm
Tools to load heap snapshot files for analysis. If it is a memory leak, you can further check the reference chain of the leaking object to GC Roots by using the tool to find out what reference path the leaking object takes and which GC Roots are associated with so that the garbage collector cannot reclaim them. Based on the type information of the leaking object and its reference chain to GC Roots, It is generally possible to pinpoint exactly where these objects are created, and thus locate the code that is causing the memory leak.
Out of memory
If it is not a memory leak, in other words, that objects in memory really must survive, you should check the Java virtual machine’s heap parameters (-Xmx and -xMS) Settings against the machine’s memory to see if there is room to adjust up. Then check whether there are some objects from the code life cycle is too long, hold state time is too long, the storage structure design is not reasonable and so on, to reduce the memory consumption of the program running period as far as possible.
The virtual machine stack and the local method stack overflow
The HotSpot virtual machine does not distinguish between the virtual machine stack and the local method stack, so for HotSpot, the -xoss parameter (setting the local method stack size) exists but has no effect and the stack size can only be set by the -xss parameter. With respect to the virtual machine stack and the local method stack, two exceptions are described in the Java Virtual Machine Specification:
- StackOverflowError is thrown if the stack depth requested by the thread is greater than the maximum depth allowed by the virtual machine.
- If the virtual machine’s stack memory allows dynamic scaling, an OutOfMemoryError is raised when sufficient memory cannot be allocated to the extended stack.
The Java Virtual Machine Specification explicitly allows the Java VIRTUAL machine to choose whether to support dynamic stack extension or not, whereas the HotSpot virtual machine does not support extension, so unless an OutOfMemoryError occurs when the thread is created and cannot obtain enough memory, Otherwise, there will be no memory overflow due to expansion while the thread is running, only StackOverflowError due to stack size not being able to accommodate the new stack frame.
The vm stack memory overflows
StackOverflowError
Sample code:
/**
* VM Args:-Xss128k
*
* @author zhengsh
* @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 exception information
Exception in thread "main"java.lang.StackOverflowError stack length:992 at cn.zhengsh.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13) at cn.zhengsh.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14) //.... Omit the moreCopy the code
OutOfMemoryError
package cn.zhengsh.jvm.oom;
/ * * *@author zhengsh
* @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 result:
stack length:6986
Exception in thread "main" java.lang.StackOverflowError
at cn.zhengsh.jvm.oom.JavaVMStackSOF2.test(JavaVMStackSOF2.java:22)
at cn.zhengsh.jvm.oom.JavaVMStackSOF2.test(JavaVMStackSOF2.java:22)
Copy the code
conclusion
Either because the stack frame is too large or the virtual machine stack size is too small, the HotSpot virtual machine throws StackOverflowError when the memory of the new stack frame cannot be allocated. However, on virtual machines that allow dynamic stack size expansion, the same code leads to a different situation.
Memory overflow caused by thread creation
Note: This experiment may cause the operating system to freeze and is recommended to be performed in a virtual machine
/**
* VM Args:-Xss512k
*
* @author zhengsh
* @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
Because the runtime constant pool is part of the method area, overflow tests for the two areas can be done together. HotSpot has been phasing in a plan to “de-perpetuate” since JDK 7, and in JDK 8 uses meta-space entirely instead of perpetuating.
Method area memory overflow
The main responsibility of the method area is to store type-related information, such as class names, access modifiers, constant pools, field descriptions, method descriptions, and so on. For this area of testing, the basic idea is that the runtime produces 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 zhengsh
* @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 String constant pool already contains a String equal to the 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 to persistent generations. We can indirectly limit the capacity of constant pools by limiting the size of persistent generations by -xx: PermSize and -xx: MaxPermSize.
/ * * *@author zhengsh
* @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
This code, when run in JDK 6, will get two false, and when run in JDK 7, will get one true and one false. The reason for the difference is that in JDK 6, the intern() method copies the first encountered string instance into the string constant pool of the persistent generation and returns a reference to that string instance, while the string object instances created by StringBuilder are stored on the Java heap. So it must not be the same reference, and the result will return false. In JDK 7 (and some other virtual machines, such as JRockit) implementations of intern() no longer need to copy instances of strings to the permanent generation. Since the string constant pool has been moved to the Java heap, it only needs to record the first instance reference in the constant pool. So the reference returned by intern() is the same as the string instance created by StringBuilder. String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String) {String(String); The string “computer software” is the first occurrence, so the result returns true.
Native direct memory overflow
The size of DirectMemory can be specified with the -xx :MaxDirectMemorySize parameter, which is the same as the Maximum Java heap size (specified by -xmx) by default. The Unsafe class’s getUnsafe() method specifies that only the bootstrap class loader will return the instance, indicating that the designers wanted only classes in the VM’s standard library to use Unsafe functionality. Broadening only made some of its functionality available to outsiders via VarHandle in JDK 10), because DirectByteBuffer allocates memory without actually asking the operating system to allocate memory, although DirectByteBuffer also throws an out-of-memory exception. Unsafe::allocateMemory() is the most important method to allocateMemory, even if it is Unsafe.
/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
*
* @author zhengsh
* @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.zhengsh.jvm.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:21)
Copy the code
The resources
- Deep Understanding of THE JVM Virtual Machine – 3rd edition
- Docs.oracle.com/javase/spec…