哪些场景会发生OOM异常

刚刚开通了一个公众号,会分享一些技术博客和自己觉得比较好的项目,同时会更新一些自己使用的工具和图书资料,后面会整理一些面试资料进行分享,觉得有兴趣的可以关注一下。
在这里插入图片描述


前言

从今天开始会整理一些常见的面试题目,博客中会涉及一些JVM参数,可以关注一下公众号,回复JVM,即可领取最新版《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》

今天要聊的就是比较经典的OOM问题。主要有5种场景,喜下面分别介绍。


1.堆内存溢出

这是比较常见的一种内存溢出的异常。

堆内存主要用来存储对象实例,我们只要不停的创建对象,并且保证GCROOTS和对象之间有可达路径避免被垃圾回收的话,那么在对象数量超过最大堆的大小限制之后,很快就能出现这个异常。我们可以通过设置堆内存位2M-Xms2m -Xmx2m,然后执行测试代码:

public class OOM {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        while (true){
            list.add(new Object());
        }
    }
}

异常描述如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.base/java.util.Arrays.copyOf(Arrays.java:3512)
	at java.base/java.util.Arrays.copyOf(Arrays.java:3481)
	at java.base/java.util.ArrayList.grow(ArrayList.java:237)
	at java.base/java.util.ArrayList.grow(ArrayList.java:244)
	at java.base/java.util.ArrayList.add(ArrayList.java:454)
	at java.base/java.util.ArrayList.add(ArrayList.java:467)
	at OOM.main(OOM.java:8)

2.栈内存异常

栈是线程私有的,他的生命周期和线程相同,每个方法在执行的同时都会创建一个栈帧,栈帧里面保存了局部变量表、操作数栈、动态链接、方法出口。那方法在调用的过程呢,其实就是栈帧入栈和出栈的过程。
在Java虚拟机规范中对虚拟机栈定义了两种异常。

  • 第一,如果线程请求的栈深度大于虚拟机所允许的深度,就会抛出StackOverflow异常。比如我们可以设置-Xss160k,那其中Xss代表每个线程的栈内存大小,通过测试发现,单线程下无论怎么设置参数,其实都是StackOverflow异常。
public class OOM {
    private static int length = 1;

    public static void main(String[] args) {
        test();
    }

    static void test(){
        System.out.println(length++);
        test();
    }
}

结果如下:

15261
Exception in thread "main" java.lang.StackOverflowError
	at java.base/java.io.FileOutputStream.write(FileOutputStream.java:349)
	at java.base/java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:81)
	at java.base/java.io.BufferedOutputStream.flush(BufferedOutputStream.java:142)
	at java.base/java.io.PrintStream.write(PrintStream.java:570)
	at java.base/sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:234)
	at java.base/sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:313)
	at java.base/sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:111)
	at java.base/java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:178)
	at java.base/java.io.PrintStream.writeln(PrintStream.java:723)
	at java.base/java.io.PrintStream.println(PrintStream.java:938)
	at OOM.test(OOM.java:12)
	at OOM.test(OOM.java:13)
  • 第二种,如果虚拟机栈可以动态扩展的话,并且扩展时无法申请到足够的内存,就会抛出OOM异常。现在我们把代码改成多线程,并且调整参数为2m,-Xss2m,就可以看见OOM异常了,因为每个线程分配的内存越大,栈空间可容纳的线程总数量就越少,越容易产生内存溢出,那反之的话如果内存不够的情况,可以调小该参数来达到支撑更多线程的目的。
public class OOM {

    public static void main(String[] args) {
        while (true){
            new Thread(() -> dontStop()).start();
        }
    }

    static void dontStop(){
        try {
            TimeUnit.HOURS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

结果如下:

JVMDUMP039I ????????"systhrow",????"java/lang/OutOfMemoryError",?? 2024/04/26 17:19:54 - ????

3.直接内存溢出

直接内存并不是虚拟机运行时数据区域的一部分,也不是《Java虚拟机规范》中内定义的内存区域,并且不受堆内存大小的限制,但是受到机器内存大小的限制。比如常见在NIO中可以使用native函数直接分配堆外内存,就有可能导致OOM的问题。那直接内存的大小可以通过参数来指定,那如果你不指定的话,他默认就和java堆内存最大值一样,直接内存导致内存溢出一个最明显的特征就是dump文件不会有明显的异常,如果发现OOM之后dump文件很小,而程序中又直接或间接使用了NIO,那么就可以考虑检查一下是不是这方面的原因。


异常信息如下:

java.lang.OutOfMemoryError: Direct buffer memory

代码里使用DirectByteBuffer之类的,如下:

public class OOM {

    public static void main(String[] args) {
        List<ByteBuffer> list = new ArrayList<>();
        while (true){
            list.add(ByteBuffer.allocate(1024*1024*20));
        }
    }
}

4. 元空间溢出

JDK8之后使用Metaspace来代替永久代,Metaspace是方法区在Hotspot的实现。Metaspace不在虚拟机内存,而是使用本地内存,也就是JDK8中的ClassMetadata,被存储在叫做Metaspacenativr memory
比如我们可以这样设置元空间的大小
-XX:MetaspaceSize=50m -XX:MaxMetaspaceSize=50m(1.8版本jdk)
1.8版本之前方法区存在于永久代,1.8版本之后取消了永久代的概念,如果是之前的版本的话,可以通过设置PermSize大小来设置永久代的大小。
代码:

public class OOM {
    public static void main(String[] args) {
        while (true){
            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, objects);
                }
            });
            enhancer.create();
        }
    }


}

结果:

Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError-->Metaspace
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:511)
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572)
	at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:387)
	at OOM.main(OOM.java:23)

5.GC OOM

GC OOM是由于JVM在GC时,对象过多导致内存溢出。
代码如下:

public class OOM {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(15);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executorService.execute(() -> {
                try {
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }


}

异常信息:

java.lang.OutOfMemoryError: GC overhead limit
  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦幻D开始

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值