欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java虚拟机三:OutOfMemoryError异常分析

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

  根据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参数中可以看到刚才添加的参数,表示设置成功:

  Java虚拟机三:OutOfMemoryError异常分析

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

  Java虚拟机三:OutOfMemoryError异常分析

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

  tomcat日志:

Java虚拟机三:OutOfMemoryError异常分析

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

  jvisualvm监控:

Java虚拟机三:OutOfMemoryError异常分析

 

   从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日志:

Java虚拟机三:OutOfMemoryError异常分析

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

接下来,将 -xss 大小设置为1m,即设置jvm参数  -xss1m ,调用接口后,再次观察tomcat日志:

Java虚拟机三:OutOfMemoryError异常分析

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

三、方法区溢出

  在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日志:

Java虚拟机三:OutOfMemoryError异常分析

 

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

  jvisualvm监控:

Java虚拟机三:OutOfMemoryError异常分析

 

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

tomcat日志:

 Java虚拟机三:OutOfMemoryError异常分析

jvisualvm监控:

Java虚拟机三:OutOfMemoryError异常分析

 

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

 

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