OOM 你知道的有哪几种?

OOM:Out Of Memory

  • java.lang.StackOverflowError
  • java.lang.OutOfMemoryError:Java heap space
  • java.lang.OutOfMemoryError:GC overhead limit exceeded
  • java.lang.OutOfMemoryError:Direct buffer memory
  • java.lang.OutOfMemoryError:unable to create new native thread
  • java.lang.OutOfMemoryError:Metaspace

StackOverflowError:栈溢出

     栈,一般默认大小范围是: 512K~1024K。

    StackOverflowError 是个错误 Error,不是异常。

    错误和异常的关系图:

由此可见,StackOverflowError  OOM 都属于错误范畴。平时我们所说的 “报 OOM 异常了” 只是口头语而已。

StackOverflowError 代码实现很简单,我们知道方法存在栈中,因此我们直接无限递归的调用一个方法就可以做到。

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

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

------------------------------
Exception in thread "main" java.lang.StackOverflowError

OutOfMemoryError:Java heap space

     内存溢出,Demo实现时最好手动分配内存大小,我设置了 10M,然后 new 了一个 20M 的 byte 数组,直接溢出。

byte[] bytes = new byte[20 * 1024 * 1024];

---------------------
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

如何手动设置内存大小在 Java 强软弱虚 中有提到。

以上两种都是最简单最最常见的 “OOM” 错误类型,下面,重点、难点 来了...

OutOfMemoryError:GC overhead limit exceeded

什么意思?照样子翻译过来是:GC 超出开销限制。

具体什么含义呢:

  • GC 回收时间过长,过长的意思就是:超过98%的时间都用来 GC 却回收了不到2%的堆内存。
  • 连续多次 GC 都回收不到2%的极端情况下才会抛出
  • 若不抛 GC overhead limit exceeded 异常,那不到2%的内存很快会被再次填满,GC不得不再次执行,导致恶性循环

代码测试之前,先将程序的内存手动分配一个很小的空间:

测试代码:

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

        try {
            while (true){
                list.add(String.valueOf(++i).intern());
            }
        }catch (Throwable throwable){
            System.out.println("i=="+i);
            throwable.printStackTrace();
            throw throwable;
        }

---------------------------
i==138412

java.lang.OutOfMemoryError: GC overhead limit exceeded
[Full GC (Ergonomics) 
[PSYoungGen: 2047K->2047K(2560K)] 
[ParOldGen: 7055K->7044K(7168K)] 9103K->9092K(9728K), 
[Metaspace: 3651K->3651K(1056768K)], 0.0734731 secs] 
[Times: user=0.38 sys=0.00, real=0.07 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

    由上代码可见,在 i 累加到 138412 时报错,也就是程序执行了 138412 次。

看打印日志

  • PSYoungGen 新生代 GC 之前内存占用 2047K,GC之后还是2047...内存总共就 2560K,GC 根本没起到作用
  • ParOldGen 老生代 GC之前占用 7055K,GC之后占 7044K,就收了11K,几乎没起到作用
  • Metaspace GC 之前是3651K,GC 之后还是3651
  • 说明系统在GC执行了 N 次之后,发现实在是 GC 不动了,没啥用,所以干脆就报个错:GC overhead limit exceeded

Metaspace :元空间

  • 本质和永久代类似,都是对JVM规范中方法区的实现
  • 不在虚拟机中,使用的是本地内存,因此只收本地内存限制

OutOfMemoryError:Direct buffer memory

       ——直接缓冲存储器内存溢出

导致原因:

  • NIO程序经常使用 ByteBuffer 来读写数据,这是一种基于通道(Channel)和缓冲区(Buffer)的IO方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作,这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native堆 中来回复制数据。
  • ByteBuffer.allocate(capability):第一种方式是分配 JVM 堆内存,属于 GC 管辖范围,由于需要拷贝所以速度相对较慢。
  • ByteBuffer.allocateDirect(capability):第二种方式是分配 OS 本地内存,不属于 GC 管辖范围,由于不需要拷贝所以速度相对较快。
  • 但如果不断分配本地内存,堆内存又很少使用,那么 JVM 就不需要 GC,DirectByteBuffer 对象们就不会被回收,这时候堆内存充足,但本地内存可能已经快用光了,如果还继续分配本地内存,最终就会导致 OOM。

配置内存参数

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

代码

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);

---------------
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

   本地内存只分配了 5m,然后初始化了一个 6m 的 ByteBuffer,直接 OOM。

OutOfMemoryError:unable to create new native thread

       ——不能创建新的本地线程。在高并发的情况下会出现,与对应的平台有关。

导致原因:

  • 创建了太多线程,超过系统承载极限
  • 服务器不允许应用程序创建这么多线程,Linux 系统默认允许单个进程可以创建的线程数量上限是 1024 个。

解决办法:

  • 降低线程数量
  • 修改 Linux 服务器配置,扩大默认限制

     伪代码:

while (true) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(Integer.MAX_VALUE);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

----------------------------
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
	at java.lang.Thread.start0(Native Method)
	at java.lang.Thread.start(Thread.java:717)
	at com.example.lpy.myapplication.javatest.MyOOMDemo.main(MyOOMDemo.java:23)

     无限地新建线程,并且让每一个线程都等待很长很长时间,这样每个线程都会存在很长很长时间,最终导致 OOM。

OutOfMemoryError:Metaspace

  • 元空间内存溢出
  • 元空间是方法区,装类的信息,各种元素等。
  • 查看 Metaspace 大小的方法:命令行输入:java -XX:+PrintFlagsInitial
  • java 8 及其之后的版本用 Metaspase 代替永久代
  • Metaspace 是方法区在 HotSpot 中的实现,它与持久代最大的区别在于:Metaspace 并不在 JVM内存中而是在本地内存

永久代(java 8之后被 Metaspace 取代)存放以下信息:

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

JVM参数:-XX:MetaspaseSize=8m  -XX:MaxMetaspaseSize=8m

伪代码:

static class OOMTest{}
public static void main(final String[] args) {

        int i = 0;
        try {
            while (true){
                i++;
                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.invokerSuper(o,args);
                    }
                });
                enhancer.create();
            }
        } catch (Throwable e) {
            System.out.println(i+" 次后发生了异常");
            e.printStackTrace();
        }
}

-------------------
276  次后发生了异常
java.lang.OutOfMemoryError:Metaspace

猜你喜欢

转载自blog.csdn.net/S_Alics/article/details/103306027
OOM