StackOverflowError 和 OutOfMemoryError 是 Java 虚拟机 (JVM) 中两种常见的错误,它们通常发生在不同的内存区域:
1. StackOverflowError:
-
发生区域:
- 虚拟机栈 (VM Stack)
- 本地方法栈 (Native Method Stack) (在 HotSpot VM 中,本地方法栈和虚拟机栈合二为一)
-
原因:
- 线程请求的栈深度大于虚拟机允许的最大深度。
- 通常是由于无限递归调用导致的。
- 也可能是方法调用层次过深(虽然较少见)。
-
示例:
public class StackOverflowExample { public static void recursiveMethod() { recursiveMethod(); // 无限递归调用 } public static void main(String[] args) { recursiveMethod(); } }运行这段代码会导致
StackOverflowError。 -
如何诊断:
- 查看异常堆栈信息,找到递归调用的方法。
- 检查递归调用的终止条件是否正确。
- 检查是否有循环依赖导致的方法无限调用.
-
如何解决:
- 修复递归调用: 确保递归调用有正确的终止条件,避免无限递归。
- 增加栈大小 (不太推荐): 可以使用
-XssJVM 参数增加线程栈的大小,但这只是推迟了问题的发生,并没有解决根本原因。通常不推荐这样做,除非你非常确定需要更大的栈空间。 - 优化代码: 将递归调用改写为迭代(循环)调用(如果可能)。
- 尾递归优化: 如果编程语言和 JVM 支持尾递归优化, 可以将递归调用改写为尾递归形式(但Java目前不支持尾递归优化).
2. OutOfMemoryError:
-
发生区域:
- 堆 (Heap):
OutOfMemoryError: Java heap space- 最常见的情况。
- 当应用程序创建的对象太多,超出了堆的最大容量,并且垃圾回收器无法回收足够的内存时,会抛出此错误。
- 方法区 (Method Area) / 元空间 (Metaspace):
- JDK 1.7 及之前 (永久代 PermGen):
OutOfMemoryError: PermGen space- 当加载的类太多、常量池太大、或者动态生成的类太多(例如,使用 CGLIB、Javassist 等动态代理库)时,可能会导致永久代溢出。
- JDK 1.8 及之后 (元空间 Metaspace):
OutOfMemoryError: Metaspace- 当加载的类太多、常量池太大、或者动态生成的类太多时,可能会导致元空间溢出。元空间使用本地内存,因此不太容易溢出,但如果应用程序加载了大量的类,仍然可能耗尽本地内存。
- JDK 1.7 及之前 (永久代 PermGen):
- 虚拟机栈 (VM Stack):
OutOfMemoryError(如果虚拟机栈可以动态扩展,但无法申请到足够的内存)- 不太常见,通常是
StackOverflowError。 - 如果虚拟机栈被配置为可以动态扩展,但操作系统无法提供足够的内存来扩展栈时,可能会抛出
OutOfMemoryError。
- 不太常见,通常是
- 本地方法栈: 类似于虚拟机栈。
- 直接内存 (Direct Memory):
OutOfMemoryError: Direct buffer memory- Java NIO (New I/O) 可以使用
ByteBuffer.allocateDirect()方法分配直接内存。 - 直接内存不受 Java 堆大小的限制,但受本机总内存大小的限制。
- 如果应用程序分配了过多的直接内存,可能会导致
OutOfMemoryError。
- Java NIO (New I/O) 可以使用
- 本地内存耗尽:
- 如果 JVM 本身或其他本地代码(例如,JNI 代码)分配了过多的本地内存,可能会导致整个进程的内存耗尽,从而抛出
OutOfMemoryError。 - 创建的线程过多, 导致内存耗尽.
- Swap 空间不足.
- 如果 JVM 本身或其他本地代码(例如,JNI 代码)分配了过多的本地内存,可能会导致整个进程的内存耗尽,从而抛出
- 堆 (Heap):
-
原因:
- 内存泄漏 (Memory Leak): 对象不再被使用,但仍然被引用,导致垃圾回收器无法回收这些对象。
- 对象过大: 创建了过大的对象(例如,大数组、大位图)。
- 对象过多: 创建了过多的对象,超出了堆的容量。
- 静态集合类持有大量对象: 例如, 使用静态的
HashMap或List存储了大量数据,并且没有及时清理。 - 资源未释放: 例如,打开的文件、数据库连接、网络连接等没有及时关闭。
- 配置问题: JVM 堆大小设置过小。
- 方法区/元空间溢出: 加载的类太多、常量池太大、动态生成的类太多。
- 直接内存溢出: 分配了过多的直接内存。
-
如何诊断:
- 分析
OutOfMemoryError的错误信息: 错误信息通常会指明是哪个区域发生了内存溢出(例如,Java heap space、PermGen space、Metaspace、Direct buffer memory)。 - 使用堆转储快照 (Heap Dump):
- 使用
-XX:+HeapDumpOnOutOfMemoryErrorJVM 参数,在发生OutOfMemoryError时自动生成堆转储快照。 - 使用
-XX:HeapDumpPath参数指定堆转储文件的保存路径. - 使用 MAT (Memory Analyzer Tool)、JProfiler、VisualVM 等工具分析堆转储快照,找出占用内存最多的对象,以及对象的引用关系,从而定位内存泄漏或其他问题。
- 使用
- 分析 GC 日志:
- 使用
-XX:+PrintGCDetails、-XX:+PrintGCDateStamps、-Xloggc:<file>等 JVM 参数开启 GC 日志。 - 分析 GC 日志,查看 GC 的频率、停顿时间、回收的内存量等信息,判断是否存在 GC 性能问题。
- 使用
- 使用 JConsole、VisualVM 等工具监控 JVM 内存使用情况。
- 分析代码: 仔细检查代码, 找出可能导致内存泄漏或创建过多对象的地方.
- 分析
-
如何解决:
- 修复内存泄漏: 找出并修复内存泄漏的代码。
- 优化对象使用:
- 避免创建过大的对象。
- 及时释放不再使用的对象(将对象的引用设置为
null)。 - 使用对象池复用对象。
- 增加堆大小: 使用
-XmxJVM 参数增加堆的最大大小(如果物理内存足够)。 - 调整方法区/元空间大小:
- JDK 1.7 及之前:使用
-XX:MaxPermSize调整永久代大小。 - JDK 1.8 及之后:使用
-XX:MaxMetaspaceSize调整元空间大小。
- JDK 1.7 及之前:使用
- 优化直接内存使用:
- 避免过度使用直接内存。
- 使用
-XX:MaxDirectMemorySize参数限制直接内存的大小。
- 优化垃圾回收器: 选择合适的垃圾回收器,并调整垃圾回收参数。
- 代码优化: 优化代码,减少内存占用。
- 使用弱引用、软引用、虚引用: 对于一些可以被回收的对象,可以使用弱引用、软引用或虚引用,以便垃圾回收器在内存不足时回收这些对象。
总结:
| 错误类型 | 发生区域 | 主要原因 |
|---|---|---|
StackOverflowError | 虚拟机栈、本地方法栈 | 无限递归调用、方法调用层次过深 |
OutOfMemoryError | 堆 | 内存泄漏、对象过大/过多、静态集合类持有大量对象、资源未释放、配置问题(堆大小设置过小) |
| 方法区/元空间 | 加载的类太多、常量池太大、动态生成的类太多 | |
| 虚拟机栈/本地方法栈 | 如果虚拟机栈/本地方法栈可以动态扩展,但无法申请到足够的内存 (通常是 StackOverflowError) | |
| 直接内存 | 分配了过多的直接内存 (java.nio) | |
| 本地内存 (Native Memory) | JVM 本身或其他本地代码分配了过多的本地内存;创建的线程过多,导致内存耗尽;Swap 空间不足 |
4207

被折叠的 条评论
为什么被折叠?



