OutOfMemoryError异常

在虚拟机规范中,除了程序计数器,其他区域都有OutOfMemoryError异常的能,下面就实践一下这些异常。

Java堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。这是一个堆内存溢出的例子:

package outofmemoryerror;

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

/**
 * @author Zhang
 * @date 2018/8/16
 * @Description  -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {
    static class OOMObject{}

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();

        while (true){
            list.add(new OOMObject());
        }
    }
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid8480.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [28211988 bytes in 0.180 secs]
异常处理

首先通过内存映像分析工具对Dump出来的堆转储快照进行分析,确认内存中的对象是否是有必要的,也就是要先分清楚到底是出现了内存泄漏还是内存溢出。

  • 内存泄漏(memory leak) :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
  • 2、内存溢出 (out of memory) :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

下面是用JProfiler对上述例子dump出来的快照进行分析的结果:
这里写图片描述
下图是内存使用情况,可以看出,内存一直在上升,没有下降的趋势,说明没有进行垃圾回收,发生了内存泄漏:
这里写图片描述
如果没有发生内存泄漏,那就应当检查-Xmx和-Xms参数,尝试调大。也可以检查代码,看是否存在某些对象生命周期过长、持有状态时间过长的情况。

虚拟机栈和本地方法栈溢出

栈容量由-Xss参数设定,下面是通过减小栈内存容量产生StackOverflow异常的例子:

package outofmemoryerror;

/**
 * @author Zhang
 * @date 2018/8/16
 * @Description  -Xss128k
 */
public class JavaVMStackSOF {
    private int stackLength = 1;

    public void stackLeak(){
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        }catch (Throwable e){
            System.out.println("Stack length:"+oom.stackLength);
            throw e;
        }
    }
}

运行结果:

Stack length:1527
Exception in thread "main" java.lang.StackOverflowError
    at outofmemoryerror.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)

结果表明在栈帧过大或栈容量太小,会抛出StackOverflowError异常。

方法区和运行时常量池溢出

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。可以在运行时产生大量的类填满方法区直到溢出。1.8以后方法区在元空间,当类加载过多后,元空间会自动扩展大小。可以设置参数-XX:MaxMetaspaceSize和-XX:MetaspaceSize控制元空间大小。

本机内存直接溢出

由DirectMemory导致的内存溢出,一个明显得特征是在HeapDump文件中不会看见明显得异常,如果Dump文件很小,程序中直接或间接使用了NIO,就可以考虑这个方面的原因。DirectMemory容量由-XX:MaxDirectMemorySize指定。下面是一个OOM异常的例子:

package outofmemoryerror;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author Zhang
 * @date 2018/8/16
 * @Description
 */
public class DirectMemoryOOM {

    public static final int _1MB = 1024*1024;

    public static void main(String[] args) throws Exception{
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true){
            unsafe.allocateMemory(_1MB);
        }
    }
}

分配堆外内存是通过DirectByteBuffer实现的,DirectByteBuffer在申请内存时,通过计算得知无法分配内存时,会抛出异常,真正分配内存的方法是Unsafe的allocataMemory分配内存,它是单例的,只有引导类加载器才能返回实例,运行结果如下所示:

Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at outofmemoryerror.DirectMemoryOOM.main(DirectMemoryOOM.java:21)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值