6

OutOfMemoryError异常 - 温暖如太阳

 2 years ago
source link: https://www.cnblogs.com/xtt321/p/16218167.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

OutOfMemoryError异常

除了程序计数器外,虚拟机内存在其他几个运行时区域都有发生OutOfMemoryError异常的可能。

Java堆溢出

设置Idea堆的大小为20MB,不可扩展(-Xms参数与最大值-Xmx参数设置为一样,避免自动扩展)

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

运行以下代码:

package memory;

import java.util.ArrayList;
import java.util.List;

public class HeepOOM {
    static class OOMObject{

    }

    public  static  void  main(String[] args){
        List<OOMObject> list = new ArrayList<>();
        while (true){
            list.add(new OOMObject());
        }
    }
}
抛出错误:

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 memory.HeepOOM.main(HeepOOM.java:14)

解决这个异常重点是确认内存中的对象是否是必要的,也就是区分是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)

  • 内存泄漏:程序申请内存后,无法释放已申请的内存空间,多次内存泄漏堆积后的后果就是内存溢出
  • 内存溢出:程序申请内存时,没有足够的内存供申请者使用

如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Root相关联导致垃圾回收器无法自动回收他们。

如果不存在泄漏,则就应该检查虚拟机堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少长期运行期的内存消耗。

虚拟机栈和本地方法栈溢出
  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,则抛出StackOverflowError异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存,则抛出OutOfMemoryError异常。
package memory;

public class JavaVmStackSOF {
    private int stackLength = 1;

    public  void  stackLeak(){
        stackLength++;
        stackLeak();
    }
            /**
     * -Xss180K -设置每个线程分配180K内存
     * @param args
     * @throws Throwable
     */
    public static void main(String[] args) throws Throwable{
        JavaVmStackSOF oom = new JavaVmStackSOF();
        try{
            oom.stackLeak();
        }catch (Throwable e){
            System.out.println("stack length:"+oom.stackLength);
            throw  e;
        }
    }
}

运行结果:

Exception in thread "main" java.lang.StackOverflowError
stack length:1554
    at memory.JavaVmStackSOF.stackLeak(JavaVmStackSOF.java:7)
package memory;

public class JavaVMStackOOM {

   private void dontStop(){
       while (true){

       }
   }

   public  void  stackLeakByThread(){
       while (true){
           Thread thread = new Thread(new Runnable() {
               @Override
               public void run() {
                   dontStop();
               }
           });
           thread.start();
       }
   }

   /**
    * -Xss2M -设置每个线程分配2M内存
    * 最终会耗尽所有内存,导致没有足够的内存创建线程而抛出异常:OutOfMemoryError
    * @param args
    * @throws Throwable
    */
   public static void main(String[] args) throws Throwable{
       JavaVMStackOOM oom = new JavaVMStackOOM();
       oom.stackLeakByThread();
   }
}
Exceptuib in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

运行时常量池溢出
运行时常量池属于方法区,因此可以通过以下方式模拟:
java7可以通过设置:-XX:PermSize=10M -XX:MaxPermSize=10M 来限定方法区。
java8之后永久代被移除,-XX:PermSize、-XX:MaxPermSize已经被移除;可以使用:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M (元空间代替)
类型信息、字段、方法、常量保存在元空间。

package main.java.loadclass;

import java.util.ArrayList;
import java.util.List;

public class RuntimeConstantPoolOOM {

            /**
     * JAVA7 下运行
     * @param args
     */
    public static void main(String[] args){
        //使用LIST保持着常量池引用,避免Full GC 回收常量池行为
        List<String> list = new ArrayList<>();
        //10MB的PermSize在integer范围内足够产生OOM了
        int i = 0;
        while (true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

抛出以下异常:java.lang.OutOfMemoryError:PermGen space

方法区溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。一个类如果被垃圾回收器回收掉,判定条件非常苛刻,在经常动态生成大量Class的应用中,需要特别注意类的回收状态。

本机直接内存溢出
DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样。
DirectByteBuffer是Java用于实现堆外内存的一个重要类,我们可以通过该类实现堆外内存的创建、使用和销毁。DirectByteBuffer跑出内存溢出异常时并没有真正整整向操作系统申请分配内存,而是通过计算得知内存无法分配,手动跑出异常。
使用Unsafe类的allocateMemory方法是真正分配内存。

package main.java.loadclass;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    /**
     * -Xmx20M -XX:MaxDirectMemorySize=10M
     * @param args
     * @throws Exception
     */
    public  static  void main(String[] args) throws Exception{
        Field unsefeField = Unsafe.class.getDeclaredFields()[0];
        unsefeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsefeField.get(null);
        while (true){
            unsafe.allocateMemory(_1MB);
        }
    }
}

返回结果:

Exception in thread "main" java.lang.OutOfMemoryError
    at java.base/jdk.internal.misc.Unsafe.allocateMemory(Unsafe.java:616)
    at jdk.unsupported/sun.misc.Unsafe.allocateMemory(Unsafe.java:462)
    at main.java.loadclass.DirectMemoryOOM.main(DirectMemoryOOM.java:16)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK