教你写Bug,常见的 OOM 异常分析


教你写Bug,常见的 OOM 异常分析

文章插图
 
在《JAVA虚拟机规范》的规定里,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生 OutOfMemoryError 异常的可能 。
本篇主要包括如下 OOM 的介绍和示例:
  • java.lang.StackOverflowError
  • java.lang.OutOfMemoryError: Java heap space
  • java.lang.OutOfMemoryError: GC overhead limit exceeded
  • java.lang.OutOfMemoryError-->Metaspace
  • java.lang.OutOfMemoryError: Direct buffer memory
  • java.lang.OutOfMemoryError: unable to create new native thread
  • java.lang.OutOfMemoryError:Metaspace
  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit
  • java.lang.OutOfMemoryError: Out of swap space
  • java.lang.OutOfMemoryError:Kill process or sacrifice child
我们常说的 OOM 异常,其实是 Error

教你写Bug,常见的 OOM 异常分析

文章插图
 
一. StackOverflowError1.1 写个 bugpublic class StackOverflowErrorDemo {    public static void main(String[] args) {        javaKeeper();    }    private static void javaKeeper() {        javaKeeper();    }}上一篇详细的介绍过
JVM 运行时数据区
,JVM 虚拟机栈是有深度的,在执行方法的时候会伴随着入栈和出栈,上边的方法可以看到,main 方法执行后不停的递归,迟早把栈撑爆了
Exception in thread "main" java.lang.StackOverflowError at oom.StackOverflowErrorDemo.javaKeeper(StackOverflowErrorDemo.java:15)
教你写Bug,常见的 OOM 异常分析

文章插图
 
1.2 原因分析
  • 无限递归循环调用(最常见原因),要时刻注意代码中是否有了循环调用方法而无法退出的情况
  • 执行了大量方法,导致线程栈空间耗尽
  • 方法内声明了海量的局部变量
  • native 代码有栈上分配的逻辑,并且要求的内存还不小,比如 java.net.SocketInputStream.read0 会在栈上要求分配一个 64KB 的缓存(64位 linux)
1.3 解决方案
  • 修复引发无限递归调用的异常代码,通过程序抛出的异常堆栈,找出不断重复的代码行,按图索骥,修复无限递归 Bug
  • 排查是否存在类之间的循环依赖(当两个对象相互引用,在调用toString方法时也会产生这个异常)
  • 通过 JVM 启动参数 -Xss 增加线程栈内存空间,某些正常使用场景需要执行大量方法或包含大量局部变量,这时可以适当地提高线程栈空间限制
二. Java heap spaceJava 堆用于存储对象实例,我们只要不断的创建对象,并且保证 GC Roots 到对象之间有可达路径来避免 GC 清除这些对象,那随着对象数量的增加,总容量触及堆的最大容量限制后就会产生内存溢出异常 。
Java 堆内存的 OOM 异常是实际应用中最常见的内存溢出异常 。
2.1 写个 bug/** * JVM参数:-Xmx12m */public class JavaHeapSpaceDemo {    static final int SIZE = 2 * 1024 * 1024;    public static void main(String[] a) {        int[] i = new int[SIZE];    }}代码试图分配容量为 2M 的 int 数组,如果指定启动参数 -Xmx12m,分配内存就不够用,就类似于将 XXXL 号的对象,往 S 号的 Java heap space 里面塞 。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:13)2.2 原因分析
  • 请求创建一个超大对象,通常是一个大数组
  • 超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值
  • 过度使用终结器(Finalizer),该对象没有立即被 GC
  • 内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收
2.3 解决方案针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可 。如果仍然没有解决,可以参考以下情况做进一步处理: