"OOM" (Out Of Memory) 是指程序在运行过程中因为内存不足而引发的错误。在 Java 中,这种错误通常表现为 Out Of Memory Error
。OOM 可以发生在不同的内存区域,具体原因取决于程序的运行情况和内存分配策略。
1、Java 堆空间溢出 (Heap Space OOM)
原因
-
对象过多:程序创建了大量对象,且这些对象的生命周期较长,导致垃圾回收器无法及时回收足够的空间。
-
大对象:程序创建了大量较大的对象,如大型数组或字符串,消耗大量连续内存。
-
内存泄漏:程序中存在不再使用的对象,但由于引用链的存在导致垃圾收集器无法回收这些对象。
-
显式内存分配:程序中使用
new
关键字显式分配内存,但未适当管理,导致内存耗尽。 -
垃圾回收效率低:由于某些原因(如内存碎片化、垃圾回收算法效率低下等),垃圾回收器无法有效回收内存。
示例
public class HeapSpaceOOM { public static void main(String[] args) { List<byte[]> list = new ArrayList<>(); while (true) { list.add(new byte[1024 * 1024]); // 分配 1MB 的字节数组 } } }
2、元数据空间溢出 (Metaspace OOM)
原因
-
类加载过多:程序加载了大量类,超过了 Metaspace 的容量限制。
-
类加载器泄漏:由于类加载器的引用链存在,导致已经加载的类无法被卸载。
-
动态代理:使用动态代理框架(如 CGLIB 或 Javassist)生成大量类。
-
反射操作:频繁使用反射操作加载类。
示例
public class MetaspaceOOM { public static void main(String[] args) { int count = 0; try { while (true) { Class.forName("com.example.MyClass" + count++); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
3、线程栈溢出 (Thread Stack OOM)
原因
-
递归调用过深:方法递归调用层次过深,超过了线程栈的深度限制。
-
线程数量过多:创建了大量线程,每个线程都占用一定的栈空间。
-
线程栈大小设置不当:线程栈大小设置过大或过小,不适合实际需要。
示例
public class ThreadStackOOM { public static void main(String[] args) { threadStackOOM(); } private static void threadStackOOM() { threadStackOOM(); // 无限递归 } }
4、直接内存溢出 (Direct Memory OOM)
原因
-
直接内存分配过多:通过
ByteBuffer.allocateDirect()
等方法分配了大量直接内存。 -
直接内存管理不当:分配的直接内存没有得到适当的释放。
示例
import java.nio.ByteBuffer; public class DirectMemoryOOM { public static void main(String[] args) { while (true) { ByteBuffer.allocateDirect(1024 * 1024); // 分配 1MB 的直接内存 } } }