Java常见内存溢出场景

参考

分析工具:https://www.cnblogs.com/zhangyaxiao/p/6678385.html    https://blog.youkuaiyun.com/adermxl/article/details/37725151

实战:https://blog.youkuaiyun.com/liu765023051/article/details/75127361 

1.Java堆溢出

不断创建对象,并保证GC Roots到这些对象之间有可达路径来避免垃圾回收,就能导致堆内存占用达到最大值产生内存溢出.

//VM ARGS: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
public class Main {
    static class OOMObject {}

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

程序产生以下异常

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid11164.hprof ...
Heap dump file created [28348961 bytes in 0.395 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)

 

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

HotSpot虚拟机并不区分虚拟机栈和本地方法栈,因此设置-Xoss参数没有用。栈容量值由-Xss参数设置。

虚拟机栈中有两种异常:

若线程请求的栈深度大于虚拟机所允许的最大深度,抛出StackOverflowError.

若虚拟机在扩展时无法申请到足够内存,抛出OutOfMemoryError

以下例子抛出StackOverflowError

//VM ARGS: -Xss128k -XX:+HeapDumpOnOutOfMemoryError
public class Main {
    private int statckLength = 1;

    public void stackLeak() {
        statckLength++;
        stackLeak(); //通过无限递归导致虚拟机栈溢出
    }

    public static void main(String[] args) {
       Main app = new Main();
       try {
           app.stackLeak();
       } catch (Throwable e) {
           System.out.println("stack length:"+app.statckLength);
           throw e;
       }
    }
}

此时抛出了OOM异常,并打印了调用次数为998

stack length:998
Exception in thread "main" java.lang.StackOverflowError
	at Main.stackLeak(Main.java:9)
	at Main.stackLeak(Main.java:10)
	at Main.stackLeak(Main.java:10)

 

注意:

单线程情况下,虚拟机栈容量太小,会导致StackOverflowError或OutOfMemoryError,

但是在多线程中,虚拟机栈容量太大,反而可能导致OutOfMemoryError,因为虚拟机栈容量越大,支持的线程数越少,最终就会因为线程过多而导致物理内存耗尽。

如下多线程代码会导致上面这种情况发生:

//VM ARGS: -Xss2M -XX:+HeapDumpOnOutOfMemoryError
public class Main {

    public void dontStop() {
        while(true) {}
    }

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

    public static void main(String[] args) {
       Main app = new Main();
       app.stackLeakByThread(); //执行会导致死机
    }
}

 

3.运行时常量池溢出

运行时常量池属于方法区的一部分,线程共享。

注意HotSpot从JDK1.7开始,已经没有永久代的分区概念,因此以前可能发生的一些OOM,现在可能不会发生了,例如下面代码。

//VM ARGS: -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+HeapDumpOnOutOfMemoryError
public class Main {
    
    public static void main(String[] args) {
        //使用List保持常量池引用,避免Full GC回收常量池
       List<String> list = new ArrayList<String>();
       //10M的PermSize在integer范围内足够产生OOM了
       int i=0;
       while(true) {
           list.add(String.valueOf(i++).intern());
       }
    }
}

这段代码在JDK1.6中会抛出OutOfMemoryError: PermGen space异常,

但是在JDK1.8中并没有抛异常,而是一直在循环执行。

另外,关于“永久代”分区,在JDK1.6中,和JDK1.7及以上的版本中,对String.intern()的处理略有不同,因为1.7开始已经没有永久代的说法了。例如下面

public class Main {
    public static void main(String[] args) {
       String str1 = new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern() == str1); //1.6中false,1.7中true
        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2); //1.6中false,1.7中false
    }
}

上面代码在1.6中会得到两个false,在1.7中则是一个true,一个false,原因是HotSpot没有了永久代,导致堆String.intern()的处理不同。

1.6中,新对象在首次创建时会拷贝进永久代,所以与堆中的对象比较返回false

1.7中,没有了永久代的说法,也不会拷贝对象了,只会在常量池中记录首次出现的实例引用,因而上面对比的其实是一个对象,故第一个对比返回true。

第二个对比返回false是因为常量池中已经有java这个字符串了,不属于首次创建,intern()返回的对象跟堆中的不是同一个对象。

4.方法区溢出

方法区用于存放Class相关信息,例如 类名,访问修饰符,常量池,字段描述,方法描述等。

实时编译生成的代码也保存在方法区,所以如Spring,Hibernate等框架使用了动态代理来对类增强时,可能会导致方法区内存溢出

5.本机直接内存溢出

本机直接内存溢出的显著特征是在Heap Dump文件中不会看见明细异常,如果OOM后Dump很小,而程序中又直接或间接使用了NIO,那可能就是发生了直接内存溢出

 

 

 

转载于:https://my.oschina.net/u/3300976/blog/3030287

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值