一、什么是 Java中的 OOM (OutOfMemoryError)
在 Java 中,OutOfMemoryError
(OOM) 是一个运行时错误,表示 JVM (Java Virtual Machine) 在执行程序时,无法为对象分配足够的内存。通常,这意味着 JVM 堆内存或其他内存区域(如方法区、直接内存等)已用尽。
OOM 错误通常发生在以下几种情况:
- 堆内存不足:程序在堆上分配对象时没有足够的空间。
- 栈内存不足:程序中方法的调用层次过深,导致栈空间不足。
- 直接内存不足:使用了 NIO(非阻塞I/O)等直接内存,但内存耗尽。
二、常见的 OOM 情况:
- 堆内存泄漏:创建过多对象或存储了不再使用的对象,导致堆内存耗尽。
- 栈内存溢出:方法调用过多,栈空间不足。
- 方法区溢出:类加载过多,导致方法区内存溢出。
以下是三种常见的 OOM 情况的示例代码:
1. 堆内存溢出 (Java Heap OOM)
堆内存溢出通常发生在程序创建了过多的对象,导致 JVM 堆内存耗尽。
public class HeapOOM {
static class LargeObject {
// 占用大量内存
private double[] memoryHog = new double[1024 * 1024];
}
public static void main(String[] args) {
while (true) {
new LargeObject(); // 持续创建 LargeObject 实例
}
}
}
解释:
- 该代码不断创建
LargeObject
实例,每个实例占用大量内存。由于堆内存不足,最终会抛出OutOfMemoryError
。 - 可以通过 JVM 参数
-Xmx
来设置堆的最大内存大小(例如:-Xmx512m
)。
2. 栈内存溢出 (StackOverflowError)
栈内存溢出通常发生在方法递归调用过深时,导致栈空间耗尽。
public class StackOOM {
public static void method() {
method(); // 递归调用自身
}
public static void main(String[] args) {
method(); // 调用递归方法
}
}
解释:
- 该代码通过递归方法不断调用自己,导致栈内存占满。当栈空间不足时,抛出
StackOverflowError
。 - 可以通过 JVM 参数
-Xss
设置栈的大小(例如:-Xss256k
)。
3. 方法区内存溢出 (Metaspace OOM)
方法区溢出通常发生在动态类加载过多时(例如大量的类加载或反射)。
public class MethodAreaOOM {
public static void main(String[] args) {
int i = 0;
try {
while (true) {
String className = "com.xxx.Person.Class" + i++;
// 动态加载类
Class<?> clazz = Class.forName(className);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
解释:
- 该代码通过动态加载类(在类路径下不断创建新的类名),导致方法区(Metaspace)内存不断消耗。
- 在 JDK 8 及以后,方法区被移到
Metaspace
(之前是PermGen
)。可以通过 JVM 参数-XX:MaxMetaspaceSize
设置方法区的最大内存大小。
注意:
- 堆内存溢出:通过不断创建对象或持有对象引用,但不释放它们,可能导致内存泄漏。
- 栈内存溢出:递归调用或大数组分配会占用栈内存,造成栈溢出。
- 方法区溢出:频繁加载大量类(尤其是反射、动态代理等场景)会导致方法区内存溢出。
三、如何避免 OOM:
- 监控和优化内存使用:
- 使用 Java 内存分析工具(如
jvisualvm
,jconsole
等)来监控和分析内存使用。 - 定期回收不再使用的对象,避免内存泄漏。
- 使用 Java 内存分析工具(如
- 优化栈深度:
- 小心深度递归,避免无限递归或过深的递归。
- 增加栈空间,使用
-Xss
参数调整栈大小。
- 合理配置 JVM 内存参数:
- 使用
-Xmx
和-Xms
设置合理的堆内存大小。 - 使用
-XX:MaxMetaspaceSize
设置方法区(Metaspace)的最大内存。
- 使用