OOM说明、场景和问题解决详解

1. 什么是OOM?

OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError。

看下关于的官方说明: Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.

意思就是说,当JVM因为没有足够的内存来为对象分配空间、并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。

2. 为什么会OOM?

为什么会没有内存了呢?原因不外乎有两点:

1)分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。

2)应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。

内存泄露:申请的内存在被使用完后没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请该内存的对象不再使用该内存,而该段内存又不能被虚拟机分配给别人用。

内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

在之前没有垃圾自动回收的日子里,比如C语言和C++语言,我们必须亲自负责内存的申请与释放操作,如果申请了内存,用完后又忘记了释放,比如C++中的new了但是没有delete,那么就可能造成内存泄露。偶尔的内存泄露可能不会造成问题,而大量的内存泄露可能会导致内存溢出。

而在Java语言中,由于存在了垃圾自动回收机制,所以,我们一般不用去主动释放不用的对象所占的内存,也就是理论上来说,是不会存在“内存泄露”的。但是,如果编码不当,比如,将某个对象的引用放到了全局的Map中,虽然方法结束了,但是由于垃圾回收器会根据对象的引用情况来回收内存,导致该对象不能被及时的回收。如果该种情况出现次数多了,就会导致内存溢出,比如系统中经常使用的缓存机制。Java中的内存泄露,不同于C++中的忘了delete,往往是逻辑上的原因泄露。

=================================================================

2.1 内存溢出和内存泄露的区别:

1、内存溢出:程序在分配内存的时候没有足够大的空间了。

2、内存泄漏:程序在申请内存之后,没有办法释放掉内存,它始终占用着内存,即被分配的对象可达但无用。内存泄露一般都是因为内存中有一块很大的对象,但是无法释放。 会导致内存溢出。

3. OOM的类型

3.1 按照JVM规范,JAVA虚拟机在运行时会管理以下的内存区域:

  • 程序计数器:当前线程执行的字节码的行号指示器,线程私有

  • JAVA虚拟机栈:Java方法执行的内存模型,每个Java方法的执行对应着一个栈帧的进栈和出栈的操作。

  • 本地方法栈:类似“ JAVA虚拟机栈 ”,但是为native方法的运行提供内存环境。

  • JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。可分为新生代,老生代。

  • 方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot中的“永久代”。

  • 运行时常量池:方法区的一部分,存储常量信息,如各种字面量、符号引用等。

  • 直接内存:并不是JVM运行时数据区的一部分, 可直接访问的内存, 比如:NIO会用到这部分。

按照JVM规范,除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。

3.2 最常见的OOM情况有以下三种 + 解决办法

  • java.lang.OutOfMemoryError: Java heap space —— > java 堆内存溢出

    • 出现OOM原因:此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。

    • 解决办法:对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms、-Xmx等修改。

    • 举例:循环调用new A() 导致堆溢出
      虚拟机参数:-Xms1M -Xmx1M -XX:+HeapDumpOnOutOfMemoryError,
      解释:将-Xmx和-Xms设置为一样可以避免堆自动扩展。
      -XX:+HeapDumpOnOutOfMemoryError, 解释:可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照,然后分析Dump文件

     while (true){
             new A().do2();
     }

  • java.lang.OutOfMemoryError: PermGen space —— > java 永久代溢出 ,即方法区溢出了

    • 出现OOM原因:一般出现于大量Class 或者 jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。另外,过多的常量,尤其是字符串,也会导致方法区溢出。

    • 解决办法:此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m 的形式修改。

    • 举例:循环调用String.intern()方法来写入常量池,常量池溢出。
      虚拟机参数:-XX:PermSize=10M -XX:MaxPermSize=10M,
      解释:表示JVM初始分配的永久代的容量和最大容量。(永久区内存不足,1.8后都在堆上。方法区=永久代,PermGen space”,即永久代)

  while (true){
    list.add(String.valueOf(i++).intern());
  }

  • java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。

    • 出现OOM原因:JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。

    • 解决办法 :可以通过虚拟机参数 -Xss 来设置栈的大小。

    • 举例:循环调用对象引用的方式实现栈溢出。
      虚拟机参数:-Xss128k,
      解释:设置虚拟机栈的大小为128kn
      在单线程下,无论栈帧太大还是虚拟机栈容量太小,内存无法分配的时候都会抛出以上错误。

  void do2(){
    do2();
  };

 

posted @ 2020-06-03 10:54  47号Gamer丶  阅读(506)  评论(0编辑  收藏  举报