JVM系列(二):深刻讲解JVM内存溢出分析!

JVM 内存溢出

一、堆内存溢出java

堆内存中主要存放对象、数组等,只要不断地建立这些对象,而且保证 GC Roots 到对象之间有可达路径来避免垃圾收集回收机制清除这些对象,当这些对象所占空间超过最大堆容量时,就会产生 OutOfMemoryError 的异常。堆内存异常示例以下:数据库

/**

设置最大堆最小堆:-Xms20m -Xmx20m

运行时,不断在堆中建立OOMObject类的实例对象,且while执行结束以前,GC Roots(代码中的oomObjectList)到对象(每个OOMObject对象)之间有可达路径,垃圾收集器就没法回收它们,最终致使内存溢出。

*/

public class HeapOOM {

static class OOMObject {

}

public static void main(String[] args) {

    List<OOMObject> oomObjectList = new ArrayList<>();

    while (true) {

        oomObjectList.add(new OOMObject());

    }

}
}
复制代码

运行后会报异常,在堆栈信息中能够看到:数组

java.lang.OutOfMemoryError: Java heap space 的信息,说明在堆内存空间产生内存溢出的异常。bash

新产生的对象最初分配在新生代,新生代满后会进行一次 Minor GC,若是 Minor GC 后空间不足会把该对象和新生代知足条件的对象放入老年代,老年代空间不足时会进行 Full GC,以后若是空间还不足以存放新对象则抛出 OutOfMemoryError 异常。网络

常见缘由:内存中加载的数据过多如一次从数据库中取出过多数据;集合对对象引用过多且使用完后没有清空;代码中存在死循环或循环产生过多重复对象;堆内存分配不合理;网络链接问题、数据库问题等。架构

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

(1)StackOverflowError:当线程请求的栈的深度大于虚拟机所容许的最大深度,则抛出StackOverflowError,简单理解就是虚拟机栈中的栈帧数量过多(一个线程嵌套调用的方法数量过多)时,就会抛出StackOverflowError异常。学习

最多见的场景就是方法无限递归调用,以下:ui

/**

设置每一个线程的栈大小:-Xss256k

运行时,不断调用doSomething()方法,main线程不断建立栈帧并入栈,致使栈的深度愈来愈大,最终致使栈溢出。

*/

public class StackSOF {

private int stackLength=1;

public void doSomething(){

        stackLength++;

        doSomething();

}

public static void main(String[] args) {

    StackSOF stackSOF=new StackSOF();

    try {

        stackSOF.doSomething();

    }catch (Throwable e){//注意捕获的是Throwable

        System.out.println("栈深度:"+stackSOF.stackLength);

        throw e;

    }

}
}
复制代码

上述代码执行后抛出:spa

Exception in thread "Thread-0" java.lang.StackOverflowError 的异常。

(2)OutOfMemoryError:若是虚拟机在扩展栈时没法申请到足够的内存空间,则抛出 OutOfMemoryError。

咱们能够这样理解,虚拟机中能够供栈占用的空间≈可用物理内存 - 最大堆内存 - 最大方法区内存,好比一台机器内存为 4G,系统和其余应用占用 2G,虚拟机可用的物理内存为 2G,最大堆内存为 1G,最大方法区内存为 512M,那可供栈占有的内存大约就是 512M,假如咱们设置每一个线程栈的大小为 1M,那虚拟机中最多能够建立 512个线程,超过 512个线程再建立就没有空间能够给栈了,就报 OutOfMemoryError 异常了。

image

栈上可以产生 OutOfMemoryError 的示例以下:

/**

设置每一个线程的栈大小:-Xss2m

运行时,不断建立新的线程(且每一个线程持续执行),每一个线程对一个一个栈,最终没有多余的空间来为新的线程分配,致使OutOfMemoryError

*/

public class StackOOM {

private static int threadNum = 0;

public void doSomething() {

    try {

        Thread.sleep(100000000);

    } catch (InterruptedException e) {

        e.printStackTrace();

    }

}

public static void main(String[] args) {

    final StackOOM stackOOM = new StackOOM();

    try {

        while (true) {

            threadNum++;

            Thread thread = new Thread(new Runnable() {

                @Override

                public void run() {

                    stackOOM.doSomething();

                }

            });

            thread.start();

        }

    } catch (Throwable e) {

        System.out.println("目前活动线程数量:" + threadNum);

        throw e;

    }

}
}
复制代码

上述代码运行后会报异常

在堆栈信息中能够看到java.lang.OutOfMemoryError: unable to create new native thread的信息,没法建立新的线程,说明是在扩展栈的时候产生的内存溢出异常。

总结:在线程较少的时候,某个线程请求深度过大,会报 StackOverflow 异常,解决这种问题能够适当加大栈的深度(增长栈空间大小),也就是把 -Xss 的值设置大一些,但通常状况下是代码问题的可能性较大;在虚拟机产生线程时,没法为该线程申请栈空间了。

会报 OutOfMemoryError 异常,解决这种问题能够适当减少栈的深度,也就是把 -Xss 的值设置小一些,每一个线程占用的空间小了,总空间必定就能容纳更多的线程,可是操做系统对一个进程的线程数有限制,经验值在 3000~5000 左右。

在 jdk1.5 以前 -Xss 默认是 256k,jdk1.5 以后默认是 1M,这个选项对系统硬性仍是蛮大的,设置时要根据实际状况,谨慎操做。

三、方法区溢出

前面说到,方法区主要用于存储虚拟机加载的类信息、常量、静态变量,以及编译器编译后的代码等数据,因此方法区溢出的缘由就是没有足够的内存来存放这些数据。

因为在 jdk1.6 以前字符串常量池是存在于方法区中的,因此基于 jdk1.6 以前的虚拟机,能够经过不断产生不一致的字符串(同时要保证和 GC Roots 之间保证有可达路径)来模拟方法区的 OutOfMemoryError 异常;但方法区还存储加载的类信息,因此基于 jdk1.7 的虚拟机,能够经过动态不断建立大量的类来模拟方法区溢出。

/**

设置方法区最大、最小空间:-XX:PermSize=10m -XX:MaxPermSize=10m

运行时,经过cglib不断建立JavaMethodAreaOOM的子类,方法区中类信息愈来愈多,最终没有能够为新的类分配的内存致使内存溢出

*/

public class JavaMethodAreaOOM {

public static void main(final String[] args){

   try {

       while (true){

           Enhancer enhancer=new Enhancer();

           enhancer.setSuperclass(JavaMethodAreaOOM.class);

           enhancer.setUseCache(false);

           enhancer.setCallback(new MethodInterceptor() {

               @Override

               public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

                   return methodProxy.invokeSuper(o,objects);

               }

           });

           enhancer.create();

       }

   }catch (Throwable t){

       t.printStackTrace();

   }

}
}
复制代码

上述代码运行后会报:

java.lang.OutOfMemoryError: PermGen space 的异常,说明是在方法区出现了内存溢出的错误。

四、本机直接内存溢出

本机直接内存(DirectMemory)并非虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域,但 Java 中用到 NIO 相关操做时(好比 ByteBuffer 的 allocteDirect 方法申请的是本机直接内存),也可能会出现内存溢出的异常。

总结

JVM内存区域划分,便于它可以更加高效的管理自身的内存。当程序中出现这种因为JVM形成的内存溢出的状况的时候,须要根据不一样的状况作不一样的分析与处理。

END

欢迎长按下图公众号:以Java架构赢天下

公众号后台回复【Java】,获取精选Java架构学习资料(视频+文件+项目实战)

相关文章
相关标签/搜索