zoukankan      html  css  js  c++  java
  • Java虚拟机三:OutOfMemoryError异常分析

      根据Java虚拟机规范,虚拟机内存中除过程序计数器之外的运行时数据区域都会发生OutOfMemoryError(OOM),本文将通过实际例子验证分析各个数据区域OOM的情况。为了更贴近生产,本次所有例子都是通过调用接口触发,并使用jvisualvm工具监控tomcat内存进行分析。

    一、Java堆溢出

      Java堆主要用于存储对象和数组实例,只要不断创建对象或者数组,并且保证CG Roots(垃圾收集器对象)到对象之间有可达路径来避免垃圾回收机制清除这些对象,在对象数量到达最大堆的限制后就会产生内存溢出异常。

      java代码:

        static class OOMObject{};
    
        /**
         * 测试Java堆OOM
         */
        private void testHeapOom(){
            List<OOMObject> oomObjects=new ArrayList<>();
            int count=0;
            boolean flag=true;
            while(flag){
                try{
                    oomObjects.add(new OOMObject());
                    count++;
                }catch (Throwable throwable){
                    flag=false;
                    System.out.println("count="+count);
                }
            }
        }

      设置Jvm参数:在tomcat的 catalina.sh 中添加 JAVA_OPTS="$JAVA_OPTS -Xms64M -Xmx128M -XX:+HeapDumpOnOutOfMemoryError" 即初始堆内存为64M,最大堆内存为128M, -XX:+HeapDumpOnOutOfMemoryError 的含义是当虚拟机内存溢出后Dump出当前内存堆转储快照,这样可以方便事后分析。

      重启tomcat后,设置jvisualvm连接,在JVM参数中可以看到刚才添加的参数,表示设置成功:

      

      切换到监控界面,在“堆”监控界面,可以看到堆大小稳定在64M,64即为我们设置的初始堆大小:

      

      接下来,调用接口触发Java堆OOM,同时监控tomcat日志以及“堆”监控界面。

      tomcat日志:

      tomcat日志中很清楚地给出提示: java.lang.OutOfMemoryError: Java heap space (内存溢出异常:Java堆空间)。

      jvisualvm监控:

       从jvisualvm监控可以看到,不断创建对象需要更多的对空间来存储对象,当使用的堆到达设置的最大值128M的时候,就触发了OOM。同时可以看到,在堆内存占用到达设置的最大堆内存之后,内存使用量又急剧下降,这是为什么呢?这是因为当发生 java.lang.OutOfMemoryError: Java heap space 之后,Java虚拟机会对整个Java堆进行Full GC,Full GC使得堆使用量急剧下降。

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

      由于在HotSpot中不区分虚拟机栈和本地方法栈,所以对于HotSpot来说,设置 -Xoss (设置本地方法栈大小)是无效的,栈容量只由 -Xss 参数设置。栈溢出测试代码如下:

    private int stackLength=1;
    /**
    * 测试栈溢出
    */
    private void testStackLeak(){
    try{
    stackLength++;
    testStackLeak();
    }catch (Throwable e){
    System.out.println("stackLength="+stackLength);
    e.printStackTrace();
    }
    }

    同时设置JVM参数: -Xss512k ,和第一步一样,调用接口后观察tomcat日志:

    可以看到,tomcat日志打印出了 java.lang.StackOverflowError 的异常,并且栈的深度为2761。

    接下来,将 -Xss 大小设置为1M,即设置JVM参数  -Xss1M ,调用接口后,再次观察tomcat日志:

    可以看到,抛出  java.lang.StackOverflowError  异常的时候,栈的深度达到了7972。通过对比,我们可以很清楚地看到 -Xss  参数的作用:设置每个线程的栈大小,当线程请求的栈深度大于此设置的时候,就会出现 java.lang.StackOverflowError 异常。

    三、方法区溢出

      在HotSpot中,从jdk8开始,方法区的实现由永久代变更为元空间(MetaSpace),元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小,但也不是无限制的,可以配置参数来进行调整。现在通过代码来进行测试分析:

      (1)新建一个类 TestMetaSpaceClass ,注意该类不包含包名:

    public class TestMetaSpaceClass {
    }

      将该类编译后的class文件放到 /mnt/class 路径下;

      (2)测试方法中不断加载该类:

            try {
                //准备url
                URL url = new File("/mnt/class").toURI().toURL();
                URL[] urls = {url};
                //获取有关类型加载的JMX接口
                ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
                //用于缓存类加载器
                List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
                while (true) {
                    //加载类型并缓存类加载器实例
                    ClassLoader classLoader = new URLClassLoader(urls);
                    classLoaders.add(classLoader);
                    classLoader.loadClass("TestMetaSpaceClass");
                    //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)
                    System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
                    System.out.println("active: " + loadingBean.getLoadedClassCount());
                    System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }

      设置JVM参数  -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=32M  ,调用接口后观察tomcat日志以及jvisualvm中Metaspace的变化曲线。

      tomcat日志:

      通过tomcat日志可以看到,抛出了 java.lang.OutOfMemoryError: Metaspace 的异常,此时总共加载的数目为5382

      jvisualvm监控:

    通过jvisualvm中Metaspace的监控也可以看到,随着类的加载,元空间使用量逐渐增加,当超过最大值32M的时候,发生了OOM,接下来,将JVM参数再调大一点,设置为 -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=48M ,再次调用接口测试:

    tomcat日志:

     

    jvisualvm监控:

     从tomcat日志和jvisualvm监控可以看出,当把参数 -XX:MaxMetaspaceSize 调大以后,可以加载更多的类。

    以上分析如有错误之处,还请各位指正,谢谢!

      

  • 相关阅读:
    SQL Server 2005 之事务日志体系 (一)
    SQL Server 2005 事务日志之逻辑结构(二)
    SQL Server 2005 清除备份 还原历史记录
    SQL Server 验证器
    SQL Server 2005 事务日志之物理体系(三)
    程序设计语言原理重要概念
    Java作业四 图形用户界面程序设计和多线程
    Java作业二 面向对象程序设计
    Java作业三 语言基础与面向对象
    Java课程学习总结
  • 原文地址:https://www.cnblogs.com/fengweiweicoder/p/10780900.html
Copyright © 2011-2022 走看看