java 虚拟机中的 outofmemoryerror 是在堆内存、方法区或线程栈等区域无法分配更多内存且垃圾回收器也无法释放空间时抛出的错误。要复现该错误,最直接的方法是编写一个不断向特定内存区域添加数据的程序,直到触发 jvm 的内存上限阈值。

如何复现堆内存溢出(Heap OOM)
堆内存是 JVM 管理对象实例的主要区域。当程序中创建的对象数量超过了堆的最大容量,且垃圾收集器尝试回收后仍无法满足新对象的分配需求时,就会抛出 java.lang.OutOfMemoryError: Java heap space。这通常发生在处理大量数据或存在内存泄漏的场景中。

import java.util.ArrayList;
import java.util.List;
public class HeapOOM {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
while (true) {
// 每次循环分配 1MB 的字节数组
list.add(new byte[1024 * 1024]);
}
}
}运行上述代码并设置 JVM 参数 -Xmx10m(限制最大堆内存为 10MB),程序会在分配第 11 个左右的数组时崩溃。因为前 10 个数组占据了大部分堆空间,而新的数组无法找到连续的空闲块,且旧对象被引用无法回收,最终导致堆溢出。
立即学习“Java免费学习笔记(深入)”;
如何复现栈内存溢出(StackOverflowError vs Stack OOM)
虽然题目问的是 OOM,但栈内存溢出通常表现为 StackOverflowError,这是由递归过深导致的。真正的栈内存溢出(即每个线程分配的内存耗尽)在单线程下较难直接通过简单代码触发,除非设置极小的线程栈大小。这里我们关注更常见的栈溢出场景,即无限递归。当方法调用层级超过栈帧允许的深度时,JVM 会抛出 StackOverflowError。如果配置了非常大的线程栈且进行极度深层的递归,也可能间接导致进程级内存不足,但在标准 JVM 行为中,栈溢出优先表现为 StackOverflowError。

public class StackRecursion {
private int depth = 0;
public void recursiveCall() {
depth++;
// 模拟一些操作以占用栈帧
String s = "test" + depth;
recursiveCall();
}
public static void main(String[] args) {
new StackRecursion().recursiveCall();
}
}运行此代码,默认情况下会在递归深度达到几千层时抛出 StackOverflowError。这是因为每个方法调用都会在栈中创建一个栈帧,包含局部变量和操作数栈。当栈空间用尽时,无法再压入新的栈帧,从而引发错误。这与堆内存溢出不同,堆溢出是对象太多,栈溢出是调用太深。
基于程序中某个输入、条件或结果的具体问题:元空间溢出
除了堆和栈,JDK 8 及以后版本中,类加载信息存储在元空间(Metaspace),它使用本地内存。当动态生成的类数量过多,或者加载了过多的第三方库且未卸载时,可能会触发 java.lang.OutOfMemoryError: Metaspace。这是一个具体的条件,即类加载器持续加载新类而不卸载,导致元空间膨胀。
import javassist.ClassPool;
import javassist.CtClass;
public class MetaspaceOOM {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
int i = 0;
while (true) {
CtClass ct = pool.makeClass("com.example.DynamicClass" + i);
ct.makeClassInitializer().setBody("System.out.println(\"init\");");
ct.toBytecode(); // 触发类定义
i++;
}
}
}这段代码使用 Javassist 动态生成类。如果不限制元空间大小(默认使用本地内存,受系统限制),随着 i 的增加,元空间会被填满,最终抛出 OutOfMemoryError: Metaspace。这展示了非堆内存区域溢出的具体情形。


















