• JVM内存溢出


    1.堆内存溢出

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

    public class ArrayDel {
        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,说明在堆内存空间产生内存溢出的异常

    新产生的对象最初分配在新生代,新生代满后会进行一次MInor GC(新生代GC,指发生在新生代的垃圾收集动作,所有的Minor GC都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂),如果Minor GC后空间不坐会把该对象和新生代满足条件的对象放入老年代,老年代空间不足时会进行Full GC(老年代GC,指发生在老年代的GC),之后如果空间还不足以存放新对象则抛出OutOfMemoryError异常。

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

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

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

    常见场景如无限递归调用,如下:

    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) {
                System.out.println("栈深度:"+stackSOF.stackLength);
                e.printStackTrace();
            }
        }
    }

    异常报错结果为:

     (2)OutOfMemoryError:如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError.

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

     栈上能够产生OutOfMemoryError的示例如下:

    public class StackOOM {
        private static int threadNum = 0;
        public void doSometing(){
            try {
                Thread.sleep(1000000);
            } 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.doSometing();
                        }
                    });
                    thread.start();
                }
            } catch (Exception e) {
                System.out.println("目前活动线程数量:"+threadNum);
                e.printStackTrace();
            }
        }
    }

    上述代码运行后会报异常,在堆栈信息中可以看到 java.lang.OutOfMemoryError: unable to create new native thread 的信息,无法创建新的线程,说明是在扩展栈的时候产生的内存溢出异常。

    总结:在线程较少的时候,某个线程请求深度过大,会报StackOverflow异常,解决这种问题可以适当加大栈的深度(增加栈空间大小),也就是吧-Xss的值设置大一些,但一般情况下是代码问题的可能性较大;在虚拟机产生线程时,无法为该线程申请栈空间了,会报OutOfMemoryError异常,解决这种问题可以适当减小栈的深度,也就是吧-Xss的值设置小一些,每个线程占用的空间小了,中空间一定就能容纳个多的线程,但是操作系统对一个进程的线程数有限,经验值在3000~5000左右。

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

    3.方法区溢出

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

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

    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 的异常,说明是在方法区出现了内存溢出的错误。

    4、本机直接内存溢出

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

     5.溢出处理方式

    1. 尝试扩大堆内存看结果(-Xms1024m -Xmx1024m)
    2. 分析内存,看下一下哪个地方出现了问题
    引文链接:https://www.zhihu.com/question/20097631/answer/952988784
    当你发现自己的才华撑不起野心时,就请安静下来学习吧
  • 相关阅读:
    Spark系列文章(三):搭建Spark开发环境IDEA
    MAC下搭建Hadoop运行环境
    Spark系列文章(二):Spark运行环境构建
    Spark系列文章(一):Spark初识
    Mac配置Maven及IntelliJ IDEA Maven配置
    《VC++深入详解》学习笔记 第十八章 ActiveX控件
    《VC++深入详解》学习笔记 第十七章 进程间通信
    Git 常用指令
    BAT脚本
    让Git的输出更友好: 多种颜色和自定义log格式
  • 原文地址:https://www.cnblogs.com/smallVampire/p/12449666.html
Copyright © 2020-2023  润新知