JVM 有那几种情况会产生 OOM(内存溢出)?
JVM 有那几种情况会产生 OOM(内存溢出)?
回答重点
Java 中常见的 OOM 情况可以概括为以下几种:
| OOM 错误类型 | 原因 | 常见解决方法 |
|---|---|---|
| Java Heap Space | 堆内存不足,常见于大量对象创建 | 优化对象使用或增大堆内存 |
| StackOverflowError | 栈空间耗尽,常见于递归或深层嵌套调用 | 优化递归逻辑或增大栈空间 |
| PermGen Space / Metaspace | 方法区或元空间不足,常见于频繁动态生成类的场景 | 增大方法区或元空间,减少类加载频率 |
| Direct Buffer Memory | 直接内存不足,常见于 NIO 操作 | 增大直接内存限制或减少直接内存使用 |
| Unable to Create New Native Thread | 线程数过多,超出系统资源限制 | 控制线程数或线程池大小,避免无限制创建新线程 |
| GC Overhead Limit Exceeded | GC 时间过长且回收内存不足 | 增大堆内存、优化内存使用、调整 GC 策略 |
StackOverflowError 虽然不算 OutOfMemoryError 但是一般可以归类一起说
扩展知识
OOM(Out of Memory) 错误表示 JVM 无法为应用程序分配足够的内存,导致程序崩溃。
堆内存溢出(Java Heap Space)
Java 堆用于存放对象实例,如果创建了过多对象,或有内存泄漏导致对象无法被垃圾回收,堆内存就会耗尽。
如果有大量创建对象或集合类的场景,持续增加数据但未释放就会产生堆内存溢出。
- 错误信息:
java.lang.OutOfMemoryError: Java heap space - 解决方法:检查对象创建逻辑,确保及时释放无用对象,或增大堆内存大小(
-Xmx参数)。
栈内存溢出(StackOverflowError)
每个线程都有独立的栈空间,栈用于存储方法调用的信息(局部变量、方法参数、返回地址等)。如果方法调用层次过深或存在无限递归,栈空间耗尽就会导致栈溢出。
常见于递归方法没有正确的退出条件、深层嵌套的方法调用场景。
- 错误信息:
java.lang.StackOverflowError - 解决方法:检查递归条件,优化递归算法或增加栈空间(
-Xss参数)。
方法区或元空间溢出(Metaspace / PermGen space)
在 Java 8 之前,方法区被实现为永久代(PermGen),用于存放类的元数据(类信息、方法信息、常量池等)。
在 Java 8 之后,永久代被替换为元空间(Metaspace),用本地内存实现。在频繁加载和卸载类的情况下(如使用大量动态生成的代理类或频繁热部署),可能导致方法区或元空间溢出。
常见于使用动态代理频繁生成类、大量反射调用或频繁热部署场景。
- 错误信息: Java 7 及之前:
java.lang.OutOfMemoryError: PermGen space;Java 8 及之后:java.lang.OutOfMemoryError: Metaspace - 解决方法:增加元空间大小(
-XX:MaxMetaspaceSize);优化代码以减少类加载和反射的频率。
直接内存溢出(Direct Buffer Memory)
Java NIO 使用直接内存(Direct Memory)来加快 I/O 操作,该内存不受 JVM 堆内存的限制。如果分配过多的直接内存,超过了设置的最大值,也会导致内存溢出。
常见于使用 NIO 操作 ByteBuffer 分配大量直接内存,或者 Netty 等框架中频繁使用直接内存场景。
- 错误信息:
java.lang.OutOfMemoryError: Direct buffer memory - 解决方法:检查直接内存的分配和释放情况,增加直接内存大小限制(
-XX:MaxDirectMemorySize),或避免过多使用直接内存。
线程数过多导致的内存溢出(Unable to Create New Native Thread)
每个线程都需要栈空间和一定的操作系统资源。如果创建过多线程而超出操作系统的资源限制,可能无法再创建新的线程,导致 OOM。
常见于创建大量线程或线程池大小过大。
- 错误信息:
java.lang.OutOfMemoryError: Unable to create new native thread - 解决方法:减少线程数,合理设置线程池的大小,避免无限制地创建新线程。
GC 执行耗时过长导致的 OOM(GC Overhead Limit Exceeded)
当 JVM 在垃圾回收上花费的时间过多且回收的内存不足以满足需要,JVM 会抛出 GC Overhead Limit Exceeded 错误,以避免长时间的垃圾回收循环。通常发生在堆内存接近耗尽但又无法完全释放的情况下。
常见于对象频繁创建和销毁导致 GC 频繁触发,内存不足导致 GC 效率低下场景。
- 错误信息:
java.lang.OutOfMemoryError: GC overhead limit exceeded - 解决方法:增大堆内存,优化代码以减少短生命周期对象的创建,或调整垃圾回收策略。