Java面试题(二十八) Java大陆最神秘的邪教之「OOM错误」圣使的前世今生

一. 常见的OOM

1. 简述OOM错误种类:

1. java.lang.StackOverflowError //这个虽然不是OOM,但是也是内存溢出错误

2. java.lang.OutOfMemoryError: Java heap space
3. java.lang.OutOfMemoryError: GC overhead limit exceeded
4. java.lang.OutOfMemoryError: Direct buffer memory
5. java.lang.OutOfMemoryError: unable to create new native thread
6. java.lang.OutOfMemoryError: Metaspace

二. 细说OOM

1. java.lang.StackOverflowError

栈空间溢出 ,递归调用,栈被撑爆了,正常大小的栈是542K~1024K

代码演示:

	// 错误产生原因:深度调用方法,导致出不来,栈爆了
    public static void main(String[] args) {

        stackOverflowError();
    }

    private static void stackOverflowError() {

        stackOverflowError();
    }

运行结果:
在这里插入图片描述
2. java.lang.OutOfMemoryError: Java heap space

堆内存溢出 
	private static void Demo02() {
        //配置 VM options: -Xms1m -Xmx1m
        //一句代码也可以
        byte[] bytes = new byte[5 * 1024 * 1024];
    }

    private static void Demo01() {
        String str = "JmStart";

        while (true) {
            //配置 VM options: -Xms1m -Xmx1m
            str += new Random().nextInt(10000000);
        }
    }
public static void main(String[] args) {
		//两种方式都可以报出异常
        //Demo01();

        //Demo02();

    }

Demo01运行结果:
在这里插入图片描述
Demo02运行结果:
在这里插入图片描述
3. java.lang.OutOfMemoryError: GC overhead limit exceeded

GC回收时间过长

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

如果不抛出,那就是GC清理的一点内存很快会被再次填满,迫使GC再次执行,这样就恶性循环。

CPU使用率一直是100%,而GC却没有任何成果。

代码演示:

	public static void main(String[] args) {

        int i = 0;
        List<String> list = new ArrayList<>();

        try {
            //配置 VM options : -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
            while (true) {
                //保证添加到集合中的对象不重复
                list.add(String.valueOf(++i).intern());
            }

        } catch (Throwable e) {
            System.out.println("===========i: " + i);
            e.printStackTrace();
            throw e;
        }
    }

运行结果:
在这里插入图片描述
4. java.lang.OutOfMemoryError: Direct buffer memory

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

ByteBuffer.allocate(capability) 第一种方式是分配JVM堆内存,属于GC管辖,由于需要拷贝所以速度较慢
ByteBuffer.alloctedDirect(capability)分配os本地内存,不属于GC管辖,不需要拷贝,速度较快
但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,
但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序直接崩溃了。
public static void main(String[] args) {

        // 配置 VM options: -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

        // 没有配置 VM 之前,MaxDirectMemory大小为1796.0M,约是我的电脑内存的四分之一
        System.out.println("配置的MaxDirectMemory: " + (sun.misc.VM.maxDirectMemory() / (double)1024 / 1024) + "M");

        ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);
    }

运行结果:
在这里插入图片描述
5. java.lang.OutOfMemoryError: unable to create new native thread

应用创建了太多线程,一个应用进程创建了多个线程,超过系统承载极限。

你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024,超过这个数量,就会报错。

解决办法:
降低应用程序创建线程的数量,分析应用给是否针对需要这么多线程,如果不是,减到最低修改linux服务器配置。

在虚拟机Linux系统中演示观看更直观。

代码演示:

public static void main(String[] args) {

        for (int i = 1; ; i++) {

            new Thread(() -> {
               try { TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); }
            }, ""+i).start();
        }
    }

运行结果:
在这里插入图片描述
6. java.lang.OutOfMemoryError: Metaspace

元空间主要存放了虚拟机加载的类的信息、常量池、静态变量、即时编译后的代码。

代码演示:

	static class OOMTest{
    }

    public static void main(String[] args) {

        int i = 0; // 记录多次报出异常

        try {

            while (true) {

                i++; // 累加

                // 配置 VM options : -XX:MetaspaceSize=5m -XX:MaxMetaspaceSize=5m
                // 使用下面的 Enhancer,需要导 cglib.jar 和 asm.jar包
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMTest.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) {
            System.out.println("===============第多少次报出异常: " + i);
            e.printStackTrace();
        }
    }

运行结果:
在这里插入图片描述

总结:如果知道了错误是怎么发生的,就知道了怎么去寻找错误,解决错误,不要怕犯错,要记住错误,大神都是错误堆起来的,共勉之!

猜你喜欢

转载自blog.csdn.net/w_x_A__l__l/article/details/107127462
今日推荐