请谈谈你对OOM的认识?

Throwable类关系

在这里插入图片描述

java.lang.StackOverFlowError

通过迭代,反复调用方法,使栈区撑爆,默认大小是512k

/**
 * Created by gysui on 2020/12/7
 */
public class StackOverFlowError {
    public static void main(String[] args) {
        stackOverFlowError();
    }

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

Exception in thread “main” java.lang.StackOverflowError

在这里插入图片描述

java.lang.OutOfMemoryError:Java heap space

说明

堆内存溢出,通过创建大对象,或者创建创建足够多的对象模拟这个错误

在这里插入图片描述

/**
 * Created by gysui on 2020/12/7
 */
public class OutOfMemoryError {
    public static void main(String[] args) {
        byte[] bytes = new byte[50*1024*1024];
    }
}

错误

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.itvip666.oom.OutOfMemoryError.main(OutOfMemoryError.java:8)

java.lang.OutOfMemoryError:GC overhead limit exceeded

GC回收时间长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC都只回收了不到2%的极端情况下才会抛出。

假设不抛出GC overhead limit错误会发生什么情况呢?
那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成恶性循环,CPU使用率一直是100%,而GC却没有任何成果。

在这里插入图片描述

配置jvm运行参数模拟该错误

-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

/**
 * Created by gysui on 2020/12/7
 * 
 */
public class GCOverheadLimit {
    public static void main(String[] args) {
        int i = 0;
        List<String> list = new ArrayList<>();
        try {
            while (true) {
                list.add(String.valueOf(++i));
            }
        } catch (Exception e) {
            System.out.println("---------------- i = " + i);
            e.printStackTrace();
        }
    }
}
  • 可以看到GC一直在进行垃圾回收,但是成果可见,回收前和回收后根本没有区别,因此GC觉得,我没有必要做无用功,因此会抛出 Exception in thread “main” java.lang.OutOfMemoryError: GC overhead limit exceeded

在这里插入图片描述

java.lang.OutOfMemoryError:Direct buffer memory(直接内存溢出)

导致原因:

写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和Native堆中来回复制数据。

ByteBuffer.allocate(capability)第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。

ByteBuffer.allocateDirect(capability)第二种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝,所以速度相对较快。

但如果不断分配内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了

模拟直接内存溢出案例

模拟直接内存溢出,需要配置如下参数

  • -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
  • XX:MaxDirectMemorySize 是直接内存大小

在这里插入图片描述

/**
 * Created by gysui on 2020/12/7
 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 * 只给内存分配5M的空间,但是使用的时候,需要6M,运行会报内存溢出
 */
public class DirectBufferMemory {
    public static void main(String[] args) {
        System.out.println("直接内存大小 MaxDirectMemorySize = " + (sun.misc.VM.maxDirectMemory()) / (double) 1024 / 1024 + "MB");
        // ByteBuffer.allocateDirect(capability)方式是分配OS本地内存,不属于GC管辖范围
        // ,由于不需要内存拷贝,所以速度相对较快。
        // Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6*1024*1024);
    }
}

java.langOutOfMemoryError:Metaspace(元空间)

Java8及之后的版本使Metaspace来代永久代

Metaspace是方法区在 HotSpot的实现,与永久代最大的区别在于: Metaspace不在虚拟机内存中而是使本地内存也即在 java8中, classe metadata( the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory

永久代 (java8后被元空间Metaspace取代了)存放如下信息:

  • 虚拟机加载类的信息
  • 常量池
  • 静态变量
  • 即时编译后的代码

模拟Metaspace空间溢出,我们不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的

JVM参数

使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:Metaspacesize为218103768(大约为20.8M)

-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=10m

代码案例

package com.itvip666.oom;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Created by gysui on 2020/12/7
 * 
 */
public class MetaspaceDemo {
    static class Test {}
    public static void main(String[] args) {
        int i = 0;
        try {
            while (true) {
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(Test.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 (Exception e) {
            System.out.println("================= i =" + i);
            e.printStackTrace();
        }
    }
}

在 for 循环中, 动态生成很多class, 最终将这些class加载到 Metaspace 中。

执行这段代码, 随着生成的class越来越多, 最后将会占满 Metaspace 空间, 抛出 java.lang.OutOfMemoryError: Metaspace. 在Mac OS X上, Java 1.8.0_05 环境下, 如果设置了启动参数 -XX:MaxMetaspaceSize=64m, 大约加载 70000 个class后JVM就会挂掉。

运行结果

在这里插入图片描述

需要引入cglib依赖

在这里插入图片描述

链接:https://pan.baidu.com/s/1WYKxAyqsMb-aBS62Qko_8Q
提取码:ybnk

java.lang.OutOfMemoryError:unable to create new native thread

错误说明

不能创建更多的线程

高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unbale to create new native thread

准确的将该native thread异常与对应的平台有关。

导致原因:
应用创建了太对线程,一个应用进程创建多个线程,超过系统承载极限。

服务器并不允许应用程序创建那么多线程,linux系统默认允许单个进程可以创建的线程数是1024个,如果应用创建超过这个数量,就会报java.lang.OutOfMemoryError:unable to create new native thread

解决办法:
想办法降低应用程序创建线程的数量,分析应用是否真的需要创建那么多线程,如果不是,改代码将线程数降到最低。

对于有的应用,确实需要创建多个线程,远超过linux系统默认的1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制。

测试代码

/**
 * Created by gysui on 2020/12/7
 * 高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unbale to create new native thread
 *
 * 导致原因:
 * 应用创建了太对线程,一个应用进程创建多个线程,超过系统承载极限
 */
public class UnbaleToCreateNewNativeThreadDemo {
    public static void main(String[] args) {
        for (int i = 0; ; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName());
                    TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },""+i).start();
        }
    }
}

以上代码是在linux系统中测试的

在这里插入图片描述

linux服务器测试

非root用户登录linux系统测试

查看最大线程数量命令:ulimit -u

centos8: 一个进程可以创建的最大进程是7718个

在这里插入图片描述

服务器级别调参调优

扩大服务器线程数:

# 这个文件可能没有需要自己创建
vim /etc/security/limits.d/90-nproc.conf

# 内容
* soft nproc 65535
* hard nproc 65535

[root@localhost limits.d]$ ulimit -u
7718
[root@localhost limits.d]# vim 90-nproc.conf
[root@localhost limits.d]# exit
exit

[zmnxzshen@localhost limits.d]$ su root
密码:
[root@localhost limits.d]# ulimit -u
8000

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

sgy_yuebin

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

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

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

打赏作者

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

抵扣说明:

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

余额充值