常见 OOM 异常场景
- 堆内存溢出java.lang.OutOfMemoryError: Java heap space
- 栈溢出-java.lang.StackOverFlowError、java.lang.OutOfMemorryError
- 元空间溢出java.lang.OutOfMemoryError: Metaspace
- 直接内存溢出java.lang.OutOfMemoryError: Direct buffer memory
- GC超限java.lang.OutOfMemoryError: GC overhead limit exceeded
查看本地 JVM 垃圾回收器
java -XX:+PrintFlagsFinal-version
前置条件
- jdk 版本为 1.8 。jdk 版本垃圾回收机制均不太一样,同样内存结构也有变化,尤其是 1.6、1.7、1.8 三个版本表现出比较明显,目前大部分企业用的是 jdk1.8 版本,本最佳实践也采用 jdk1.8 版本作为基础,如果是其他版本的jdk,可以借鉴。
- 接入ARMS 。
栈溢出 java.lang.StackOverFlowError
启动参数
-javaagent:C:\Users\Administrator\Downloads\ctyunArmsAgent\ctyunArmsAgent.jar -Dotel.resource.attributes=service.name=arms_example -Darms.licenseKey=${licenseKey}-Dotel.exporter.otlp.endpoint=${endpoint}
APM监控
堆溢出 java.lang.OutOfMemoryError: Java heap space
启动参数
-Xmx100m -javaagent:C:\Users\Administrator\Downloads\ctyunArmsAgent\ctyunArmsAgent.jar -Dotel.resource.attributes=service.name=arms_example -Darms.licenseKey=${licenseKey} -Dotel.exporter.otlp.endpoint=${endpoint}
APM监控
直接内存溢出 java.lang.OutOfMemoryError: Direct buffer memory
参数:
-Xmx100m-XX:MaxMetaspaceSize=100M -javaagent:C:\Users\Administrator\Downloads\ctyunArmsAgent\ctyunArmsAgent.jar -Dotel.resource.attributes=service.name=arms_example -Darms.licenseKey=${licenseKey}-Dotel.exporter.otlp.endpoint=${endpoint}
APM监控
代码:
package com.arms.example.controller;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@RequestMapping("/exec")
@RestController
public class ExceptionController {
@GetMapping("/heapOOM")
public void heapOOM() {
List<LargeObject> list = new ArrayList<>();
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new LargeObject());
}
}
@GetMapping("/stackOOM")
public void stackOOM() {
while (true) {
Thread thread = new Thread(() -> {
while (true) {
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
@GetMapping("/directBufferOOM")
public void directBufferOOM() throws InterruptedException {
final int _1M = 1024 * 1024 * 1;
List<ByteBuffer> buffers = new ArrayList<>();
int count = 1;
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);
buffers.add(byteBuffer);
System.out.println(count++);
Thread.sleep(1000);
}
}
@GetMapping("/stackOverflow")
public void StackOFE() {
stackOverFlowErrorMethod();
}
public static void stackOverFlowErrorMethod() {
stackOverFlowErrorMethod();
}
@GetMapping("/metaspaceOOM")
public void metaspaceOOM() {
try {
List<Class<?>> generatedClasses = new ArrayList<>();
MyClassLoader classLoader = new MyClassLoader();
while (true) {
String className = "com.arms.example.controller.GeneratedClass"+System.nanoTime();
byte[] classData = generateClassData(className);
Class<?> generatedClass = classLoader.defineClass(className, classData);
generatedClasses.add(generatedClass);
}
} catch (OutOfMemoryError e) {
System.out.println("Metaspace OOM occurred!");
e.printStackTrace();
}
}
private static byte[] generateClassData(String className) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className.replace('.','/'), null, "java/lang/Object", null);
MethodVisitor constructor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructor.visitCode();
constructor.visitVarInsn(Opcodes.ALOAD, 0);
constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructor.visitInsn(Opcodes.RETURN);
constructor.visitMaxs(1, 1);
constructor.visitEnd();
// 生成一个方法,输出 "Hello, World!"
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "sayHello", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello, World!");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 0);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
static class MyClassLoader extends ClassLoader {
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("com.example.generated")) {
return super.findClass(name);
}
return super.loadClass(name);
}
public Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
public class LargeObject {
private byte[] data;
public LargeObject(){
this(256);
}
public LargeObject(int sizeInKB) {
int sizeInBytes = sizeInKB * 1024;
data = new byte[sizeInBytes];
}
}
}