臭名昭著,怙恶不悛的OOM,到底是什么?

欢迎关注微信公众号:互联网全栈架构

很多Java程序员都经常会碰到OOM,而这类问题往往比较难以解决,找到root cause并不是件容易的事情,我们今天就来看看OOM到底是什么?有哪些常见的OOM,初步的解决方法有哪些?

OOM是Out Of Memory的缩写,翻译过来就是内存溢出,在程序运行过程中,如果出现了OOM,就会抛出OutOfMemoryError异常。在JVM运行时数据区中,只有程序计数器这一个区域不会出现OOM异常:

f6e1f748df3831d2445616313a583d80.png

常见的OOM有如下这些:

03b0f0873ede9f0299efcbfb7b202b68.png

一、Java heap space:可能最常见的OOM异常,表示Java堆内存溢出,示例:

package com.sample.core.oom;
// 堆内存溢出
public class HeapOOM {
    public static void main(String[] args) {
        String[] str = new String[10000 * 10000];
    }
}

然后指定VM选项,把最大堆内存设置为10M:

c1f86219ce61c18f4311037566a405dd.png

运行代码,就会出现OOM的异常:

37dc380c885844b3357525e9322e6feb.png

可通过增加堆内存、分析代码找出分配过多对象等方式来解决。比如上面的代码,如果运行的VM选项Xmx设置为1000M,那么程序运行的时候就不会报错。

二、GC Overhead limit exceeded:表示垃圾回收一直在运行从而导致程序本身运行缓慢。在垃圾回收后,如果Java进行花费89%的时间用于GC且恢复不到2%的内存,那么就会抛出这种类型的OOM。示例:

package com.sample.core.oom;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
// 设置VM参数为-Xmx10M,可以很快看到效果
public class OverheadExceedOOM {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        Random random = new Random();
        while(true){
            map.put(random.nextInt(), "互联网全栈架构");
        }
    }
}

对于这种类型的OOM,可以通过增加堆内存大小来解决,也可以添加这个VM参数来禁用这个检查:-XX:-UseGCOverheadLimit,当然,这个不会真正解决问题,只是把问题延后,最终还是出现Java heap space的OOM。当然,彻底的解决办法还是分析代码,看看到底是哪里导致了这样的问题。

三、Requested array size exceeds VM limit:顾名思义,这个错误表示数组大小超过了VM的限制,像这种OOM相对比较少见,一般情况下,也很少会声明一个数组,用于存放上亿个元素。

四、Metaspace:Java类的元数据(Java类在虚拟机中的内部表示)在堆外内存进行分配,如果存入Java类元数据的元空间用尽了,那么就会抛出这个错误,可以用参数MaxMetaSpaceSize来指定元空间的最大值。示例:

package com.sample.core.oom;

public class MetaspaceOOM {
    static javassist.ClassPool cp
            = javassist.ClassPool.getDefault();

    public static void main(String args[]) throws Exception {
        // 循环加载类
        for (int i = 0; i < 100000; i++) {
            Class c = cp.makeClass("com.sample.core.oom.MetaspaceOOM" + i)
                    .toClass();
        }
    }
}

Javassist是一个开源的分析、编辑和创建Java字节码的类库。使用它需要在pom.xml中引入以下的依赖:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.28.0-GA</version>
</dependency>

指定VM的参数-XX:MaxMetaspaceSize=10M,运行代码,可以看到以下的OOM:

39d42b49901a90e1ebbff35d2b3d5089.png

五、unable to create new native thread:当前JVM申请创建一个新的线程,但底层操作系统无法分配新的OS线程时,会抛出这样的异常。示例:

package com.sample.core.oom;

public class NewThreadOOM {
    public static void main(String[] args) {
        while(true){
            new Thread(() -> {
                System.out.println(Math.random() +" ");
                try {
                    Thread.sleep(10000000);
                } catch(InterruptedException e) { }
            }).start();
        }
    }
}

由于操作系统的差异,以上代码在Linux平台上进行测试,运行一段时间后,会出现以下的异常:

a7e1bd8d90b4338d1b838090e3d815f2.png

六、request size bytes for reason. Out of swap space:当本地内存不够用时会抛出此异常,可通过日志文件排查问题的根本原因。通常,这个异常跟底层的操作系统有关系,比如没有配置足够的交换空间,或者别的进程消耗了所有的内存资源。

七、Compressed class space:在64位平台上,可以使用32位的偏移指针来访问数据,它通过参数UseCompressedClassPointers来进行控制(参数默认是开启的),如果它所需的空间超过了参数CompressedClassSpaceSize指定的值,就会抛出Compressed class space的OOM异常。

八、reason stack_trace_with_native_method:在JNI(Java Native Interface)或者本地方法遇到了资源分配失败时,会抛出此异常。

以上就是关于OOM的基本介绍,以及OOM的常见类型,对于最常遇到的情况,文章给出了样例代码进行演示,后续文章会继续介绍OOM的一些排查手段等内容,敬请期待。

都看到这里了,请帮忙一键三连啊,也就是点击文末的在看、点赞、分享,这样会让我的文章让更多人看到,也会大大地激励我进行更多的输出,谢谢!

鸣谢:

https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks002.html

https://www.geeksforgeeks.org/understanding-outofmemoryerror-exception-java/

周志明.《深入理解Java虚拟机》.机械工业出版社,2011

推荐阅读:

ZooKeeper集群安装

俯拾皆是的Java注解,你真的get了吗?

“八面玲珑”的ZooKeeper入门介绍

责无旁贷:超酷的责任链模式

聚沙成塔:聊聊建造者模式

公司裁员,码农竟然成了“帮凶”?(剧情杜撰)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值