OutOfMemoryError异常的几种原因

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

在IDEA中添加JVM参数如下:

da957dbc1a9eae7d5675a658cc7a56e9ad7.jpg         c7073d3391d5367e8ea1250866bdcbf4c06.jpg

 

一、Java堆溢出

        Java堆主要是用来存储对象,系统中不断的创建对象,并且在GC Roots到对象之间有可达路径,使垃圾回收机制不会回收这些对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。代码如下:

/**
 * JVM参数:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\test\heapdump.hprof
 * -Xms:堆的最小内存
 * -Xmx:堆的最大内存
 * -XX:+HeapDumpOnOutOfMemoryError :出现内存溢出时Dump出当前内存堆转储快照
 * -XX:HeapDumpPath= :快照的存放路径
 */
public class HeapOOM {

    static class   OOMObject{}

    public static void main(String[] args) {
        List<OOMObject> list =  new ArrayList<OOMObject>();
        //不停的创建OOMObject
        while (true){
            list.add(new OOMObject());
        }
    }

}

    运行结果:

f2a6760881dd5eb71c884f9213b362abd76.jpg

    堆内存溢出在实际应用中还是很常见的,出现堆内存溢出时,异常信息“java.lang.OutOfMemoryError”后会进一步提示 “Java heap space”。出现这个异常可以通过分析工具分析,确认时内存泄漏还是内存溢出。如果是泄露的话,继续使用工具分析具体泄露位置;不存在泄露,看看是不是可以加大堆内存容量。

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

    HotSpot虚拟机不区分虚拟机栈和本地方法栈,所以设置本地方法栈的参数-Xoss并没有效果;栈的容量只由-Xss参数设定。关于栈,Java虚拟机规范描述了两种异常:①如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。②如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。我们这里仅测试StackOverflowError异常如下:

/**
 * JVM参数:-Xss128k
 * 默认栈容量深度为:19029
 * 修改栈容量为128k深度为:1001
 * 通过减小栈的容量,可见栈的深度也变小。
 */
public class JavaVMStackSOF {

    private int stackLength = 1;

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

    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;
        }
    }
}

    运行结果:(不同机器测试结果数量可能不同)

默认情况下: 68bbdd0262febde1bba708099c5e825fb19.jpg                                  -Xss128k:  3e3eac77f14c672cb4a8702df60f52676e5.jpg

       虚拟机使用默认参数,栈深度在大多数情况下,完全够用了(包括递归)。

三、方法区(元数据区)溢出

        在JDK7之前方法区可以通过PermSize永久代大小和MaxPermSize最大永久代大小设置,如:-XX:PermSize=10m -XX:MaxPermSize=10m 。但是在JDK8中已经完全移除了永久代,PermSize和MaxPermSize参数也一并移除了。在移除了Perm区域之后,JDK8中使用MetaSpace来替代,这些空间都直接在堆上来进行分配。  在JDK8中,类的元数据存放在native堆中,这个空间被叫做:元数据区。JDK8中给元数据区添加了一些新的参数:

    ①-XX:MetaspaceSize=<NNN> :<NNN>是分配给类元数据区(以字节计)的初始大小(初始高水位),超过会导致垃圾收集器卸载类。这个数量是一个估计值。当第一次到达高水位的时候,下一个高水位是由垃圾收集器来管理的。

    ②-XX:MaxMetaspaceSize=<NNN> :<NNN>是分配给类元数据区的最大值(以字节计)。这个参数可以用来限制分配给类元数据区的大小。这个值也是个估计值。默认无上限。

    ③-XX:MinMetaspaceFreeRatio=<NNN>:<NNN>是一次GC以后,为了避免增加元数据区(高水位)的大小,空闲的类元数据区的容量的最小比例,不够就会导致垃圾回收。

    ④  -XX:MaxMetaspaceFreeRatio=<NNN>:<NNN>是一次GC以后,为了避免减少元数据区(高水位)的大小,空闲的类元数据区的容量的最大比例,超过就会导致垃圾回收。

    借助CGLib使元数据区出现内存溢出测试如下:

/**
 * JVM参数:-XX:MetaspaceSize=5M -XX:MaxMetaspaceSize=5M (JDK8)
 * -XX:PermSize=10m -XX:MaxPermSize=10m (JDK7)
 *  需要引入cglib依赖
 */
public class JavaMethodAreaOOM {

    public static void main(final String[] args) {
        while (true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                public Object intercept(Object obj, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
                    return proxy.invokeSuper(obj,args);
                }
            });
            enhancer.create();
        }
    }

    static class OOMObject{}

}

    运行结果:

d31bd73e4b0e18ceefa2868e689a305cfa4.jpg

        在很多主流框架中都是用到了CGLib,如Spring、Hibernate,需要增强的类越多,就需要越大的元数据区来保证动态生成的Class可以加载入内存。

四、本机直接内存溢出

        本机直接内存溢出(程序中直接或简介使用了NIO会导致),不像上面几种OutOfMemoryError会告诉我们溢出的位置,如下:

/**
 * JVM参数:-Xmx20m -XX:MaxDirectMemorySize=10m
 */
public class DircetMemoryOOM {

    private static final int _1MB = 1024*1024;

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

    运行结果:

        0f8640a3d4eaccc924d61ca474b89b4cfff.jpg

 

 

源代码:https://gitee.com/itcaofanqi/CaoFanqiStudyRepository/tree/master/stujvm

参考:周志明《深入理解Java虚拟机》

 

转载于:https://my.oschina.net/caofanqi/blog/3015544

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值