常⻅ OOM 异常分析
---------作者: 捡⽥螺的⼩男孩
常⻅ OOM 异常分析
- 堆溢出
- 栈溢出
- 方法区溢出
- 本机直接内存溢出
- GC overhead limit exceeded
一、堆溢出
Java 堆⽤于存储对象实例,只要不断地创建对象,并且保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么
在对象数量到达最⼤堆的容量限制后就会产⽣内存溢出异常。
原因
- 无法在java堆中分配对象
- 应用程序保存了无法被GC回收的对象
- 应用程序过度使用finalizer
排查解决思路
-
查找关键报错信息,如java.lang.OutOfMemoryError: Java heap space。
-
使⽤内存映像分析⼯具(如 Eclipsc Memory Analyzer 或者 Jprofiler)对 Dump 出来的堆储存快照进⾏分析,分析清楚是内存泄漏还是内存溢出。
-
如果是内存泄漏,可进⼀步通过⼯具查看泄漏对象到 GC Roots 的引⽤链,修复应⽤程序中的内存泄漏。
-
如果不存在泄漏,先检查代码是否有死循环,递归等,再考虑⽤ -Xmx 增加堆⼤⼩。
二、栈溢出
关于虚拟机栈和本地⽅法栈,在 Java 虚拟机规范中描述了两种异常:
-
如果线程请求的栈深度⼤于虚拟机所允许的深度,将抛出 StackOverflowError 异常;
-
如果虚拟机栈可以动态扩展,当扩展时⽆法申请到⾜够的内存时会抛出 OutOfMemoryError 异常。
原因
-
在单个线程下,栈帧太⼤,或者虚拟机栈容量太⼩,当内存⽆法分配的时候,虚拟机抛出StackOverflowError 异常。
-
不断地建⽴线程的⽅式会导致内存溢出。
排查解决思路
- 查找关键报错信息,确定是 StackOverflowError 还是 OutOfMemoryError;
- 如果是 StackOverflowError,检查代码是否递归调⽤⽅法等;
- 如果是 OutOfMemoryError,检查是否有死循环创建线程等,通过 - Xss 降低的每个线程栈⼤⼩的容量。
三、⽅法区溢出
⽅法区,(⼜叫永久代,JDK8 后,元空间替换了永久代),⽤于存放 Class 的相关信息,如类名、访问修饰符、常量池、字段描述、⽅法描述等。运⾏时产⽣⼤量的类,会填满⽅法区,造成溢出。
原因
-
使⽤ CGLib ⽣成了⼤量的代理类,导致⽅法区被撑爆。
-
在 Java7 之前,频繁的错误使⽤ String.intern ⽅法。
-
⼤量 jsp 和动态产⽣ jsp。
-
应⽤⻓时间运⾏,没有重启。
排查解决思路
- 检查是否永久代空间设置得过⼩;
- 检查代码是否频繁错误得使⽤ String.intern ⽅法;
- 检查是否跟 jsp 有关;
- 检查是否使⽤ CGLib ⽣成了⼤量的代理类;
- 重启⼤法,重启 JVM。
四、本机直接内存溢出
直接内存并不是虚拟机运⾏时数据区的⼀部分,也不是 Java 虚拟机规范中定义的内存区域。但是,这部分内存也被频繁地使⽤,⽽且
也可能导致 OOM。
在 JDK1.4 中新加⼊了 NIO(New Input/Output) 类,它可以使⽤ native 函数库直接分配堆外内存,然后通过⼀个存储在 Java 堆中的
DirectByteBuffer 对象作为这块内存的引⽤进⾏操作。这样能在⼀些场景中显著提⾼性能,因为避免了在 Java 堆和 Native 堆中来回复
制数据。
原因
-
本机直接内存的分配虽然不会受到 Java 堆⼤⼩的限制,但是受到本机总内存⼤⼩限制。
-
直接内存由 -XX:MaxDirectMemorySize 指定,如果不指定,则默认与 Java 堆最⼤值(-Xmx 指定)⼀样。
-
NIO 程序中,使⽤ ByteBuffer.allocteDirect(capability) 分配的是直接内存,可能导致直接内存溢出。
排查解决思路
- 检查代码是否恰当。
- 检查 JVM 参数 - Xmx,-XX:MaxDirectMemorySize 是否合理。
五、GC overhead limit exceeded
-
这个是 JDK6 新加的错误类型,⼀般都是堆太⼩导致的。
-
Sun 官⽅对此的定义:超过 98% 的时间⽤来做 GC 并且回收了不到 2% 的堆内存时会抛出此异常。
解决方案
- 检查项⽬中是否有⼤量的死循环或有使⽤⼤内存的代码,优化代码。
- 检查 JVM 参数 - Xmx -Xms 是否合理。
- dump 内存,检查是否存在内存泄露,如果没有,加⼤内存。
六、总结
⼏种常⻅ OOM 异常:
- java.lang.OutOfMemoryError: Java heap space
- java.lang.OutOfMemoryError: unable to create new native thread
- java.lang.OutOfMemoryError: Metaspace
- java.lang.OutOfMemoryError: Direct buffer memory
- java.lang.OutOfMemoryError: GC overhead limit exceeded