工作中最常见的6种OOM问题

前言

最近我写的几篇线上问题相关的文章:《糟糕,CPU100%了》《如何防止被恶意刷接口》《我调用第三方接口遇到的13大坑》,发表之后,在全网广受好评。

今天接着线上问题这个话题,跟大家一起聊聊线上服务出现OOM问题的6种场景,希望对你会有所帮助。

1 堆内存OOM

堆内存OOM是最常见的OOM了。

出现堆内存OOM问题的异常信息如下:

makefile 复制代码
java.lang.OutOfMemoryError: Java heap space

此OOM是由于JVM中heap的最大值,已经不能满足需求了。

举个例子:

typescript 复制代码
public class HeapOOMTest {

    public static void main(String[] args) {
        List<HeapOOMTest> list = Lists.newArrayList();
        while (true) {
            list.add(new HeapOOMTest());
        }
    }
}

这里创建了一个list集合,在一个死循环中不停往里面添加对象。

执行结果:出现了java.lang.OutOfMemoryError: Java heap space的堆内存溢出。

很多时候,excel一次导出大量的数据,获取在程序中一次性查询的数据太多,都可能会出现这种OOM问题。

我们在日常工作中一定要避免这种情况。

2 栈内存OOM

有时候,我们的业务系统创建了太多的线程,可能会导致栈内存OOM。

出现堆内存OOM问题的异常信息如下:

arduino 复制代码
java.lang.OutOfMemoryError: unable to create new native thread

给大家举个例子:

typescript 复制代码
public class StackOOMTest {
    public static void main(String[] args) {
        while (true) {
            new Thread().start();
        }
    }
}

使用一个死循环不停创建线程,导致系统产生了大量的线程。

执行结果:如果实际工作中,出现这个问题,一般是由于创建的线程太多,或者设置的单个线程占用内存空间太大导致的。

建议在日常工作中,多用线程池,少自己创建线程,防止出现这个OOM。

3 栈内存溢出

我们在业务代码中可能会经常写一些递归调用,如果递归的深度超过了JVM允许的最大深度,可能会出现栈内存溢出问题。

出现栈内存溢出问题的异常信息如下:

复制代码
java.lang.StackOverflowError

例如:

typescript 复制代码
public class StackFlowTest {
    public static void main(String[] args) {
        doSamething();
    }

    private static void doSamething() {
        doSamething();
    }
}

执行结果:

出现了java.lang.StackOverflowError栈溢出的错误。

我们在写递归代码时,一定要考虑递归深度。即使是使用parentId一层层往上找的逻辑,也最好加一个参数控制递归深度。防止因为数据问题导致无限递归的情况,比如:id和parentId的值相等。

4 直接内存OOM

直接内存不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。

它来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存,是属于堆外内存,可以直接向系统申请的内存空间。

出现直接内存OOM问题时异常信息如下:

arduino 复制代码
java.lang.OutOfMemoryError: Direct buffer memory

例如下面这样的:

csharp 复制代码
public class DirectOOMTest {

    private static final int BUFFER = 1024 * 1024 * 20;

    public static void main(String[] args) {
        ArrayList<ByteBuffer> list = new ArrayList<>();
        int count = 0;
        try {
            while (true) {
                // 使用直接内存
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
                list.add(byteBuffer);
                count++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            System.out.println(count);
        }
    }
}

执行结果:

会看到报出来java.lang.OutOfMemoryError: Direct buffer memory直接内存空间不足的异常。

最近就业形式比较困难,为了感谢各位小伙伴对苏三一直以来的支持,我特地创建了一些工作内推群, 看看能不能帮助到大家。

你可以在群里发布招聘信息,也可以内推工作,也可以在群里投递简历找工作,也可以在群里交流面试或者工作的话题。

进群方式

添加,苏三的私人微信 :su_san_java,备注:掘金+所在城市,即可加入。

5 GC OOM

GC OOM是由于JVM在GC时,对象过多,导致内存溢出,建议调整GC的策略。

出现GC OOM问题时异常信息如下:

bash 复制代码
java.lang.OutOfMemoryError: GC overhead limit exceeded

为了方便测试,我先将idea中的最大和最小堆大小都设置成10M:

diff 复制代码
-Xmx10m -Xms10m

例如下面这个例子:

java 复制代码
public class GCOverheadOOM {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                }
            });
        }
    }
}

执行结果:

出现这个问题是由于JVM在GC的时候,对象太多,就会报这个错误。

我们需要改变GC的策略。

在老代80%时就是开始GC,并且将-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)设置的更合理。

6 元空间OOM

JDK8之后使用Metaspace来代替永久代,Metaspace是方法区在HotSpot中的实现。

Metaspace不在虚拟机内存中,而是使用本地内存也就是在JDK8中的ClassMetadata,被存储在叫做Metaspace的native memory。

出现元空间OOM问题时异常信息如下:

makefile 复制代码
java.lang.OutOfMemoryError: Metaspace

为了方便测试,我修改一下idea中的JVM参数,增加下面的配置:

ini 复制代码
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

指定了元空间和最大元空间都是10M。

接下来,看看下面这个例子:

typescript 复制代码
public class MetaspaceOOMTest {
    static class OOM {
    }

    public static void main(String[] args) {
        int i = 0;
        try {
            while (true) {
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOM.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o, args);
                    }
                });
                enhancer.create();
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

执行结果:程序最后会报java.lang.OutOfMemoryError: Metaspace的元空间OOM。

这个问题一般是由于加载到内存中的类太多,或者类的体积太大导致的。

好了,今天的内容先分享到这里,下一篇文章重点给大家讲讲,如何用工具定位OOM问题,敬请期待。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。

相关推荐
全栈派森2 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse2 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭3 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架3 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱3 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
☞无能盖世♛逞何英雄☜4 小时前
Flask框架搭建
后端·python·flask
进击的雷神4 小时前
Perl语言深度考查:从文本处理到正则表达式的全面掌握
开发语言·后端·scala
进击的雷神4 小时前
Perl测试起步:从零到精通的完整指南
开发语言·后端·scala
豌豆花下猫5 小时前
Python 潮流周刊#102:微软裁员 Faster CPython 团队(摘要)
后端·python·ai
秋野酱5 小时前
基于javaweb的SpringBoot驾校预约学习系统设计与实现(源码+文档+部署讲解)
spring boot·后端·学习