JVM 有那几种情况会产生 OOM(内存溢出)?

Sherwin.Wei Lv8

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
  • 解决方法:增大堆内存,优化代码以减少短生命周期对象的创建,或调整垃圾回收策略。
Comments